PROJECTS

GEE: GEE ENCRYPTION EDITORwritten in Python

Read or edit symmetrically encrypted secrets, with options to symmetrically encrypt or decrypt files and create new encrypted text files. All files are encrypted with a XChaCha20 stream cipher and Poly1305 MAC authentication. GEE uses the Python Cryptographic Authority’s PyNaCl bindings of the Sodium library.

The XChaCha20-Poly1305 secret key encryption (a.k.a. symmetric key encryption) provided by PyNaCl is analogous to a safe: anyone who has the key (derived from your passphrase) can open it and view the contents. Because of the Poly1305 MAC authentication, any attempts to tamper with the contents are detected.

INDEX

REQUIREMENTS

Program requirements:

NOTE: PyNaCl will be automatically installed through the installation process below, but you do need the following programs installed (in addition to Python)...

Installation requirements:

Optional programs to more securely remove plaintext temporary files:

INSTALLATION

Once the requirements are set up, GEE can easily be installed directly from the source code with the following command:

pipx install git+https://0xacab.org/cryptext/gee.git

If you have torsocks installed, you could download the source code through Tor too:

torsocks pipx install git+https://0xacab.org/cryptext/gee.git

To upgrade GEE, you can use the following:

pipx upgrade gee

And to uninstall:

pipx uninstall gee

DESIGN

  • allow for the decryption, editing, and re-encryption of XChaCha20-Poly1305 symmetrically encrypted text

  • allow for the creation of new text files to be symmetrically encrypted with XChaCha20-Poly1305

  • allow for the XChaCha20-Poly1305 symmetric encryption of any type of file, e.g. video, audio, images, private PGP keys, etc.

  • allow for the decryption of any type of XChaCha20-Poly1305 symmetrically encrypted file, e.g. video, audio, images, private PGP keys, etc.

  • allow for any text editor to be used to read or edit XChaCha20-Poly1305 encrypted text files

  • create a temporary, plaintext file---held only in memory---of the decrypted text in order to read or edit it, then delete the temporary file from memory after re-encryption

  • overwrite temporary plaintext files held in memory with random data, if possible, before deletion

  • store XChaCha20-Poly1305 encrypted text file output in printable, URL- and filesystem-safe ASCII characters

  • derive the key for the XChaCha20-Poly1305 encryption through a combination of a user-supplied passphrase and random salt using the argon2id hash function [And, therefore, the pause you might experience after entering a passphrase is due to the key stretching].

WORKFLOW EXAMPLES

  • use as a password manager by creating a encrypted text document of passwords that can be updated and re-encrypted

  • keep a folder of encrypted text documents, like a notebook or chapters of a book

  • send a secret message to friends encrypted by a shared passphrase

  • add an extra layer of encryption to messages sent to others through an encrypted messenger

  • encrypt the contents of an email message with someone who can decrypt it with a shared password

  • encrypt a video, audio, or image file before sending it to another or copying it to an external hard drive

  • etc...

USAGE

gee [-h] [-v] [--xsalsa20-poly1305] [-t EDITOR | -n EDITOR] [-c | -e | -d] FILE

Create, edit, encrypt, or decrypt files with symmetric XChaCha20 stream cipher 
encryption and Poly1305 MAC authentication.

positional arguments:
  FILE                  file to edit, create, encrypt, or decrypt

options:
  -h, --help            show this help message and exit
  -v, --version         show program's version number and exit
  --xsalsa20-poly1305   use XSalsa20-Poly1305 instead of XChaCha20-Poly1305

editor:
  Supply type and name of editor to use. If wrong type is supplied, program
  will not work correctly.

  -t EDITOR, --terminal EDITOR
                        specify a terminal-based text editor
  -n EDITOR, --non-terminal EDITOR
                        specify a non-terminal-based text editor

additional options:
  `--create` requires an EDITOR to be specified

  -c, --create          create new text FILE
  -e, --encrypt         encrypt FILE
  -d, --decrypt         decrypt FILE to hard drive

MANAGING SECRETS

In the following command-line usage examples, EDITOR is the name of the text editor you wish to use. FILE is the name of the file (including its path, if necessary) to create, edit, encrypt, or decrypt. Reading or editing a text file is the default operation. You can also create a new text file through your editor of choice. The encrypt or decrypt options apply to any type of file, e.g. video, audio, image, etc.

To read or edit a XChaCha20-Poly1305 symmetrically encrypted text file with a text editor:

gee -t EDITOR FILE

Create a XChaCha20-Poly1305 symmetrically encrypted text file with a text editor:

gee -t EDITOR -c FILE

Symmetrically encrypt any type of file with XChaCha20-Poly1305:

gee -e FILE

WARNING: Do not try to encrypt files larger than your available memory. The encryption will fail or, even worse, freeze all the processes on the computer when it runs out of memory. A future releases will hopefully deal with this memory limitation.

Nonetheless, to decrypt that file:

gee -d FILE

TODO OR POSSIBLE ENHANCEMENTS

  • add ability to encrypt files larger than available memory

  • add ability to encrypt entire directories

  • add ability to use a key file in addition to passphrase

  • etc...

F.A.Q.

Send encrypted questions!

LIMITATIONS

  • cannot encrypt files larger than available memory because the encryption will fail or, even worse, freeze all the processes on the computer when it runs out of memory

  • filename, size, length, and date of creation of message or file are not encrypted

  • if a file is decrypted (using the -d or --decrypt options) and saved to a hard drive or any storage media that file is no longer protected by encryption and would need to be properly wiped from drive in order to be completely erased

  • anything decrypted, whether temporarily or not, could be potentially viewed by anyone with access to your computer

  • if the program is interrupted (e.g. CTRL-C), it may leave plaintext data in the temporary folder of your operating system

  • other currently unknown limitations...?

WARNINGS

  • please try the program before using it with sensitive material to make sure it is the right program for your needs (please report any bugs you encounter)

  • this program has not been independently audited

SEE ALSO

Keyringer:
https://0xacab.org/rhatto/keyringer
http://wmj5kiic7b6kjplpbvwadnht2nh2qnkbnqtcv3dyvpqtz7ssbssftxid.onion/rhatto/keyringer

NIHILIST CIPHER (PYTHON) written in Python

HISTORICAL CONTEXT

The first nihilist movement began in 1860s Russian and was based in the rejection of all totalitarianisms. Its name was derived from the Latin nihil, meaning "nothing". Their influences can be found in many philosophical, cultural, and aesthetic forms of life.

In the history of cryptography, the nihilist cipher is a manually operated symmetric encryption cipher originally used by Russian nihilists in the 1880s to communicate...

The encipherer uses a key to construct a mixed alphabet Polybius square. (Though, this program constructs a mixed alphanumeric Polybius square, similar to fig. 1 below.)

1 2 3 4 5 6
1 A B C D E F
2 G H I J K L
3 M N O P Q R
4 S T U V W X
5 Y Z 0 1 2 3
6 4 5 6 7 8 9

fig. 1: alphanumeric Polybius square example

The square is then used to convert both a second key and the plaintext message into a series of two digit numbers. These numbers are then added together to obtain the ciphertext.

For information in general, you can read more about the nihilist cipher.

WARNING: This nihilist cipher software is for historical purposes only. Nowadays, it is easily decipherable and there is much better encryption software out there. Do not use this software for anything but entertainment.


import itertools
import argparse
import string
import re


class NihilistCipher:
    """All nihilist cipher methods needed to encrypt or decrypt a message."""

    def __init__(self, polybius_key, key, message):
        """Assign user input values locally."""
        self.polybius_key = polybius_key
        self.key = key
        self.message = message
        # Initialize empty Polybius square and keyword cipher
        self.polybius_square = {}
        self.key_cipher = []

    def create_polybius_square(self):
        """Create Polybius square with user-supplied polybius_key."""
        # Add Polybius key characters to square without repeating any
        square = []
        for character in self.polybius_key:
            if character not in square:
                square.append(character)
        # Add missing characters to square following Polybius key characters
        alphanumeric = list(string.ascii_uppercase) + list(string.digits)
        square += [
            character for character in alphanumeric if character not in square
        ]
        # Assign characters their respective numbers to complete square
        square_numbers = []
        square_numbers += itertools.chain(range(11, 17), range(21, 27),
                                          range(31, 37), range(41, 47),
                                          range(51, 57), range(61, 67))
        self.polybius_square = dict(zip(square_numbers, square))

    def key_encrypt(self):
        """Convert user-supplied key into numbers from Polybius square.

        Key numbers need to then be repeated for the length of the plaintext or
        cipher message so they can eventually be added or subtracted
        number-by-number (letter-by-letter) from the numbers of the message.
        """
        # Assign each key character its respective Polybius square number
        key_numbers = [
            key
            for key, value in self.polybius_square.items()
            for character in self.key
            if value == character
        ]
        # Repeat key numbers in case plaintext or cipher message is
        # longer than key
        self.key_cipher = itertools.cycle(key_numbers)

    def encrypt(self):
        """Encrypt plaintext message."""
        self.create_polybius_square()
        self.key_encrypt()
        # Assign each plaintext character its respective Polybius square number
        self.message = [
            key
            for character in self.message
            for key, value in self.polybius_square.items()
            if value == character
        ]
        # Create cipher message by adding plaintext numbers and key numbers
        # Print cipher as a string
        cipher_message = [x + y for x, y in zip(self.message, self.key_cipher)]
        print('Cipher: ' + ' '.join(str(number) for number in cipher_message))

    def decrypt(self):
        """Decrypt cipher message."""
        self.create_polybius_square()
        self.key_encrypt()
        # Decrypt by first subtracting key numbers from cipher numbers
        # Then find character values in Polybius square for each number result
        # Print decrypted message as a string
        decrypted_cipher = [
            x - y for x, y in zip(self.message, self.key_cipher)
        ]
        decrypted_result = []
        # All cipher numbers have passed number validation during argument
        # parsing but we now have to check if the numbers--after subtraction--
        # also pass the same test of being one of all possible numbers
        for number in decrypted_cipher:
            if number not in self.polybius_square:
                print(
                    'nihilist_cipher.py: error: at least one of your '
                    'cipher numbers is mathematically impossible: check again'
                )
                return
            decrypted_result.append(self.polybius_square[number])
        print('Message: ' + ''.join(decrypted_result))


def user_input():
    """Gather user input through parsed arguments."""
    # Function to validate cipher numbers to be decrypted
    def number_validation(numbers):
        # Variables to only accept numbers from all possible numbers
        included_set = set()
        for entry in range(22, 133):
            included_set.add(str(entry))
        # First, check to see if user typed anything not a number
        letters = re.search('[^0-9]+', numbers)
        if letters is not None:
            raise argparse.ArgumentTypeError(
                'you are trying to decrypt: only numbers are allowed.'
            )
        # If there are only numbers make sure user has correct numbers and
        # parsed them correctly
        numbers = re.findall('[0-9]+', numbers)
        for number in numbers:
            if number not in included_set:
                raise argparse.ArgumentTypeError(
                    'you either entered incorrect numbers or did not properly '
                    'separate them with spaces.'
                )
        return numbers

    # Parse arguments
    parser = argparse.ArgumentParser(
        description='Encrypt or decrypt a message using a nihilist cipher',
        epilog='WARNING: This nihilist cipher software is for historical '
              'purposes only and is not secure since, for instance, it is '
              'likely any plaintext of keys and messages are stored in your '
              'bash history, which can be a security risk in some instances. '
              'Also, nowadays, the nihilist cipher itself is easily '
              'decipherable. There is much better encryption sofware. Do not '
              'use this software for anything but entertainment.'
    )
    arguments = parser.add_argument_group('keys', 'necessary cipher keys')
    # Obtain necessary Polybius key to construct Polybius square
    arguments.add_argument(
        '-p',
        '--polybius-key',
        required=True,
        help='key to construct Polybius square (only letters and numbers '
            'considered, no spaces allowed)'
    )
    # Obtain necessary key to encrypt or decrypt message
    arguments.add_argument(
        '-k',
        '--key',
        required=True,
        help='key to encrypt or decrypt a message (only letters and numbers '
            'considered, no spaces allowed)'
    )
    # Obtain message to encrypt or decrypt
    # To encrypt or decrypt is a necessary choice
    actions = parser.add_mutually_exclusive_group(required=True)
    actions.add_argument(
        '-e',
        '--encrypt',
        metavar='MESSAGE',
        nargs='+',
        help='use to encrypt a plaintext MESSAGE (only letters and numbers '
            'considered)'
    )
    actions.add_argument(
        '-d',
        '--decrypt',
        metavar='NUMBERS',
        nargs='+',
        type=number_validation,
        help='use to decrypt cipher NUMBERS (numbers must be separated with '
            'spaces)'
    )
    return parser


def main():
    """Call encryption or decryption methods based on user input."""
    parser = user_input()
    args = parser.parse_args()

    args.polybius_key = args.polybius_key.upper()
    args.key = args.key.upper()

    if args.encrypt:
        args.encrypt = str(args.encrypt)
        args.encrypt = args.encrypt.upper()
        call = NihilistCipher(args.polybius_key, args.key, args.encrypt)
        call.encrypt()
    elif args.decrypt:
        cipher = [str(number) for entry in args.decrypt for number in entry]
        args.decrypt = list(int(number) for number in cipher)
        call = NihilistCipher(args.polybius_key, args.key, args.decrypt)
        call.decrypt()


if __name__ == '__main__':
    main()
              
NIHILIST CIPHER (JAVASCRIPT)written in JavaScript

HISTORICAL CONTEXT

The first nihilist movement began in 1860s Russian and was based in the rejection of all totalitarianisms. Its name was derived from the Latin nihil, meaning "nothing". Their influences can be found in many philosophical, cultural, and aesthetic forms of life.

In the history of cryptography, the nihilist cipher is a manually operated symmetric encryption cipher originally used by Russian nihilists in the 1880s to communicate...

The encipherer uses a key to construct a mixed alphabet Polybius square. (Though, this program constructs a mixed alphanumeric Polybius square, similar to fig. 1 below.)

1 2 3 4 5 6
1 A B C D E F
2 G H I J K L
3 M N O P Q R
4 S T U V W X
5 Y Z 0 1 2 3
6 4 5 6 7 8 9

fig. 1: alphanumeric Polybius square example

The square is then used to convert both a second key and the plaintext message into a series of two digit numbers. These numbers are then added together to obtain the ciphertext.

For information in general, you can read more about the nihilist cipher.

This message will self-destruct...

WARNING: This nihilist cipher software is for historical purposes only. Nowadays, it is easily decipherable and there is much better encryption software out there. Do not use this software for anything but entertainment.

POLYBIUS KEYWORD
*required
CIPHER KEYWORD
*required
PLAINTEXT MESSAGE
CIPHER MESSAGE

'use strict'

// User input values
let polybiusKeyword = ''
let keyword = ''
let plainInput = ''
let cipherInput = ''

document.getElementById('PolybiusKeyword').addEventListener('input',
  function () {
    polybiusKeyword = document.EncryptForm.PolybiusKeyword.value
    polybiusKeyword = polybiusKeyword.toUpperCase().match(/[A-Z]/g)
  }
)
document.getElementById('Keyword').addEventListener('input',
  function () {
    keyword = document.EncryptForm.Keyword.value
    keyword = keyword.toUpperCase().match(/[A-Z]/g)
  }
)
document.getElementById('PlainInput').addEventListener('input',
  function () {
    plainInput = document.EncryptForm.PlainInput.value
    plainInput = plainInput.toUpperCase().match(/[A-Z0-9]/g)
  }
)
document.getElementById('CipherInput').addEventListener('input',
  function () {
    cipherInput = document.EncryptForm.CipherInput.value
    cipherInput = cipherInput.match(/\d+/g)
  }
)

// Other global variables
let polybiusSquare = {}
let keywordCipherRepeat = []
let input = []

// Cipher functions
function createPolybiusSquare () {
  let polybiusLetters = []
  // Add polybiusKeyword letters to beginning of square without repeat letters
  polybiusKeyword.map(x => !polybiusLetters.includes(x) &&
    polybiusLetters.push(x))
  // Add remaining alphabet to square
  let letters = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
    'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
  letters.map(x => !polybiusLetters.includes(x) && polybiusLetters.push(x))
  // Add numbers set to end of square
  polybiusLetters = polybiusLetters.concat([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
  // Assign respective polybiusSquareNumbers to letters and numbers for square
  let polybiusSquareNumbers = [11, 12, 13, 14, 15, 16, 21, 22, 23, 24, 25, 26,
    31, 32, 33, 34, 35, 36, 41, 42, 43, 44, 45, 46, 51, 52, 53, 54, 55, 56, 61,
    62, 63, 64, 65, 66]
  polybiusLetters.map((x, index) => {
    polybiusSquare[x] = polybiusSquareNumbers[index]
  })
}

function keywordEncryptAndRepeat () {
  let keywordCipher = []
  // Assign polybiusSquareNumbers to keyword letters
  keyword.map(x => keywordCipher.push(polybiusSquare[x]))
  // Repeat keyword cipher for the length of the plain or cipher input
  let index = 0
  while (keywordCipherRepeat.length < input.length) {
    keywordCipherRepeat.push(keywordCipher[index])
    index < keywordCipher.length - 1 ? index++ : index = 0
  }
}

function reset () {
  // Clear all variables and form inputs
  polybiusKeyword = ''
  keyword = ''
  plainInput = ''
  cipherInput = ''
  polybiusSquare = {}
  keywordCipherRepeat = []
  input = []
  document.EncryptForm.PolybiusKeyword.value = ''
  document.EncryptForm.Keyword.value = ''
  document.EncryptForm.PlainInput.value = ''
  document.EncryptForm.CipherInput.value = ''
  // Set timer to clear results in 10 seconds
  setTimeout(function eraseResults () {
    document.getElementById('Results').innerHTML = ''
  }, 10000)
  // Create countdown timer
  document.getElementById('Timer').innerHTML = 10
  let seconds = 10
  function timer () {
    seconds = seconds - 1
    document.getElementById('Timer').innerHTML = '0' + seconds
  }
  let timerInterval = setInterval(timer, 1000)
  // Set to clear timer in 10 seconds
  setTimeout(function clearTimerInterval () {
    clearInterval(timerInterval)
    document.getElementById('Timer').innerHTML =
      '00'
  }, 10000)
  setTimeout(function eraseZeros () {
    document.getElementById('Timer').innerHTML = ''
  }, 10074.6)
}

function encryptText () {
  createPolybiusSquare()
  // Create plain text cipher as input
  plainInput.map(x => input.push(polybiusSquare[x]))
  keywordEncryptAndRepeat()
  // Create nihilist cipher by adding plain text cipher to keywordCipherRepeat
  let cipherText = []
  input.map((x, index) => cipherText.push(x + keywordCipherRepeat[index]))
  document.getElementById('Results').innerHTML =
    cipherText.toString().replace(/,/g, ' ')
  reset()
}

function decryptCipherNumbers () {
  createPolybiusSquare()
  // Assign nihilist cipher to input
  input = cipherInput
  keywordEncryptAndRepeat()
  // Decrypt nihilist cipher by subtracting keywordCipherRepeat
  let decryptCipher = []
  input.map((x, index) => decryptCipher.push(x - keywordCipherRepeat[index]))
  // Find keys by their values, join into a string
  document.getElementById('Results').innerHTML = decryptCipher.map(x =>
    Object.keys(polybiusSquare).find(key => polybiusSquare[key] === x)).join('')
  reset()
}
              
POMODORO CLOCKwritten in Vue.js/HTML/CSS
SESSION
MINUTES
BREAK
MINUTES

BUG: To work properly, clock must be reset after you stop it, and stopped before reset.

WARNING: Routing networks, such as Tor or I2P, may affect the timing of the clock bell rendering it useless.


var sessionMinutes = 25
var sessionSeconds = 0
var breakMinutes = 5
var title = 'SESSION'

var bell = {
  bellDing: document.getElementById('Bell')
}

function notifyWork() {
  if (!('Notification' in window)) {
    alert('This browser does not support desktop notification')
  } else if (Notification.permission === 'granted') {
    var notification = new Notification('GET TO WORK!')
  } else if (Notification.permission !== 'denied') {
    Notification.requestPermission().then(function (permission) {
      if (permission === 'granted') {
        var notification = new Notification('GET TO WORK!')
      }
    })
  }
}

function notifyBreak() {
  if (!('Notification' in window)) {
    alert('This browser does not support desktop notification')
  } else if (Notification.permission === 'granted') {
    var notification = new Notification('TAKE A BREAK!')
  } else if (Notification.permission !== 'denied') {
    Notification.requestPermission().then(function (permission) {
      if (permission === 'granted') {
        var notification = new Notification('TAKE A BREAK!')
      }
    })
  }
}

const sessionApp = new Vue ({
  el: '#PomodoroClock',
  data: {
    button: 'START',
    running: false,
    sessionDisplay: sessionMinutes,
    breakDisplay: breakMinutes,
    titleDisplay: title,
    timerDisplay: sessionMinutes + ':0' + sessionSeconds,
    currentSessionMinutes: sessionMinutes,
    currentSessionSeconds: sessionSeconds,
    currentBreakMinutes: breakMinutes
  },
  methods: {
    displayTimer: function () {
      let minutes = ''
      let seconds = ''

      this.currentSessionMinutes < 10 ?
        minutes = '0' + String(this.currentSessionMinutes) :
        minutes = String(this.currentSessionMinutes)
      this.currentSessionSeconds < 10 ?
        seconds = '0' + String(this.currentSessionSeconds) :
        seconds = String(this.currentSessionSeconds)
      this.timerDisplay = minutes + ':' + seconds
    },
    sessionDec: function () {
      if (this.sessionDisplay > 1) {
        this.sessionDisplay = this.sessionDisplay - 1
        sessionMinutes = this.sessionDisplay
        this.currentSessionMinutes = this.sessionDisplay
      } this.displayTimer()
    },
    sessionInc: function () {
      if (this.sessionDisplay < 60) {
        this.sessionDisplay = this.sessionDisplay + 1
        sessionMinutes = this.sessionDisplay
        this.currentSessionMinutes = this.sessionDisplay
      } this.displayTimer()
    },
    breakDec: function () {
      if (this.breakDisplay > 2) {
        this.breakDisplay = this.breakDisplay - 1
        breakMinutes = this.breakDisplay
        this.currentBreakMinutes = this.breakDisplay
      }
    },
    breakInc: function () {
      if (this.breakDisplay < 60) {
        this.breakDisplay = this.breakDisplay + 1
        breakMinutes = this.breakDisplay
        this.currentBreakMinutes = this.breakDisplay
      }
    },
    countdownMinutes: function () {
      if (this.currentSessionMinutes > 0) {
        this.currentSessionMinutes = this.currentSessionMinutes - 1
      } else {
        breakMinutes = breakMinutes - 1
        this.currentSessionMinutes = breakMinutes
        bell.bellDing.play()
        this.titleDisplay = 'BREAK'
      }
      if (breakMinutes === 0) {
        this.currentSessionMinutes = sessionMinutes - 1
        breakMinutes = this.currentBreakMinutes 
        bell.bellDing.play()
        this.titleDisplay = 'SESSION'
      }

      if (this.titleDisplay === 'BREAK' && this.timerDisplay === '00:00') {
        notifyBreak()
      } else if (this.titleDisplay === 'SESSION' &&
                this.timerDisplay === '00:00') {
        notifyWork()
      }
    },
    countdownSeconds: function () {
      if (this.currentSessionSeconds === 0) {
        this.currentSessionSeconds = 60
        this.currentSessionSeconds = this.currentSessionSeconds - 1
      } else {
        this.currentSessionSeconds = this.currentSessionSeconds - 1
      }  
    },
    timer: function () {
      if (this.running) {
        this.button = 'START'
        clearInterval(minuteInterval)
        clearInterval(secondInterval)
        clearInterval(displayInterval)
        this.currentSessionMinutes = this.currentSessionMinutes + 1
        this.currentSessionSeconds = this.currentSessionSeconds + 1
        this.running = false
        return
      }
      if (this.running === false) {
        this.button = 'STOP'
        this.countdownMinutes()
        this.countdownSeconds()
        minuteInterval = setInterval(this.countdownMinutes, 60000)
        secondInterval = setInterval(this.countdownSeconds, 1000)
        displayInterval = setInterval(this.displayTimer, 1)
        this.running = true
      }
    },
    reset: function () {
      sessionMinutes = 25
      sessionSeconds = 0
      breakMinutes = 5
      title = 'SESSION'
      this.button = 'START'
      this.sessionDisplay = sessionMinutes
      this.breakDisplay = breakMinutes
      this.titleDisplay = title
      this.timerDisplay = sessionMinutes + ':0' + sessionSeconds
      this.currentSessionMinutes = sessionMinutes
      this.currentSessionSeconds = sessionSeconds
      this.currentBreakMinutes = breakMinutes
    }
  }
})
              
VUE.JS CALCULATORwritten in Vue.js/HTML/CSS

const vm = new Vue ({
  el: '#Calculator',
  data: {
    temp: '',
    output: '0',
    display: '0'
  },
  methods: {
    input: function (value) {
      if (value === '.' && this.temp.includes('.')) {
        return this.display
      } else if (value === '0' && this.display === '0') {
        return this.display
      } else if (this.display === '0') {
        this.output = value
        this.display = value
      } else {
        this.output += value
        this.display += value
      } this.temp = ''
    },
    operator: function (opr) {
      this.temp += this.display
      this.output += opr
      this.display += opr
    },
    percentage: function () {
      this.output += '/100'
      this.display += '%'
    },
    remove: function () {
      if (this.output.charAt(this.output.length - 4) === '/') {
        this.output = this.output.substring(0, this.output.length - 3)
      }
      
      if (this.display.length > 1) {
        this.output = this.output.substring(0, this.output.length - 1)
        this.display = this.display.substring(0, this.display.length - 1)
      } else {
        this.temp = ''
        this.output = '0'
        this.display = '0'
      }
    },
    clear: function () {
      this.temp = ''
      this.output = '0'
      this.display = '0'
    },
    equals: function () {
      window.onerror = function () {
        alert('SYNTAX ERROR: Try again.')
        return true
      }
      this.output = eval(this.output.replace(/[^-()\d*+./]/g, '')).toString()
      this.display = this.output
    }
  }
})
              
PHILOSOPHY RANDOM QUOTE GENERATORwritten in JavaScript/HTML/CSS
The School of Athens painting

**SORRY. JAVASCRIPT MUST BE ENABLED TO RECEIVE A QUOTE.**

WIKIPEDIA SEARCH ENGINEwritten in JavaScript/HTML/CSS
MARKDOWN PREVIEWERwritten in JavaScript/HTML/CSS

MARKDOWN

PREVIEW

MARKDOWN QUICK REFERENCE

This guide is a very brief overview, with examples, of the syntax that Markdown supports. It is itself written in Markdown. It's shown as text and not rendered HTML.

SIMPLE TEXT FORMATTING

You can use stars or underscores for italics. Double stars and double underscores do bold. Three together do both.

For paragraphs, just have a blank line between chunks of text.

This chunk of text is in a block quote. Its multiple lines will all be indented a bit from the rest of the text.

Multiple levels of block quotes also work.

Sometimes you want to include some code, such as when you are explaining how <h1> HTML tags work, or maybe you are a programmer and you are discussing someMethod().

If you want to include some code and have newlines preserved, indent the line with a tab or at least four spaces.

   Extra spaces work here too.
                      This is also called preformatted text and it is useful for showing examples.
                        The text will stay as text, so any *markdown* or <u>HTML</u> you add will
                          not show up formatted. This way you can show markdown examples in a
                      markdown document.
You can also use preformatted text with your block quotes
                      as long as you add at least five spaces.

HEADINGS

There are a couple of ways to make headings. Using three or more equals signs on a line under a heading makes it into an "h1" style heading. Three or more hyphens under a line makes it a slightly smaller "h2" heading. You can also use multiple pound symbols before and after a heading. Pounds after the title are ignored. Here are some examples:

This is H1

This is H2

This is H1

This is H2

This is H3 with some extra pounds

You get the idea

I don't need extra pounds at the end
H6 has the maximum pound signs possible (but is smallest in size)

LINKS

Let's link to a few sites.

First, let's use the bare URL, like https://wikipedia.org.

Next, is an inline link to DuckDuckGo.

And this is a reference-style link to Wikipedia.

Lastly, like so Privacy Guides. This one and the reference-style link both automatically use the links defined below, but they could be defined anywhere in the markdown and are removed from the HTML. The names are also case insensitive, so you can use PrIvAcY GuiDEs and have it link properly too.

Title attributes appear when you hover over a link and may be added to links in various ways.

Inline links would be like this Philosophy, which give the link an "Philosophy" title.

And for reference-style, you can write them like this, Philosophy.

And of course there is, Philosophy.

LISTS

  • This is a bulleted list
  • Great for shopping lists
  • You can also use hyphens
  • Or plus symbols

The above is an unordered list. For ordered lists...

  1. Numbered lists are also easy
  2. Just start with a number
  3. However, the actual number doesn't matter when converted to HTML.
  4. This will still show up as 4.

You might want a few advanced lists:

  • This top-level list is wrapped in paragraph tags

  • This generates an extra space between each top-level item.

  • You do it by adding a blank line

    • This nested list also has blank lines between the list items.
  • How to create nested lists

    1. Start your regular list
    2. Indent nested lists with four spaces
    3. Further nesting means you should indent with four more spaces
      • This line is indented with eight spaces.
  • List items can be quite lengthy. You can keep typing and either continue them on the next line with no indentation.

  • Alternately, if that looks ugly, you can also indent the next line a bit for a prettier look.

  • You can put large blocks of text in your list by just indenting with four spaces.

    This is formatted the same as code, but you can inspect the HTML and find that it's just wrapped in a <p> tag and won't be shown as preformatted text.

    You can keep adding more and more paragraphs to a single list item by adding the traditional blank line and then keep on indenting the paragraphs with four spaces. You really need to only indent the first line, if you don't mind how it looks.

  • Lists support block quotes

    Just like this example here. By the way, you can nest lists inside block quotes!

    • Fantastic!
  • Lists support preformatted text

      You just need to indent eight spaces.

EVEN MORE

Horizontal Rule

If you need a horizontal rule you just need to put at least three hyphens, asterisks, or underscores on a line by themselves. You can also even put spaces between the characters.




Those three all produced horizontal lines. Keep in mind that three hyphens under any text turns that text into a heading, so add a blank like if you use hyphens.

IMAGES

Images work exactly like links, but they have exclamation points in front. They work with references and titles too.

The School of Athens and The School of Athens.

FURTHER REFERENCE

For more syntax, please visit the official Markdown documentation.