diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2018 O̶ ̽̒̌̋͂͌͆͐͛͝N̨̨̨̟̘̭̘̮͒͘ ̛̛̻̞ͥͦ͂̊̅͑Lͯͯ́̆̎͊̐̋͛̊ ̶̰̘͍͎ͬͪ̌̊̕Y͕͙͔̟̠̠̯͈̌̽ ̹̜͔̟̪̮̌̈́̓͠M̹̣̓͘͝͞ ̨͎̲͙̼O̧̹̓̉͊͋̇ͨ̄͞ ̵̡̦̦̘͐̑͠N͗͑͑͋͂̿ͭ̑̈̃ ̝̭̳ͭ̃͆͛I͚̬͖̟̪͓̯ͬͪ͜ ̬̗͎͎̮̺̑̈́̓͡Ḵ̷̀ͯ́ͮ͐ͦ͝͞ ̭͓͙͙ͬ̈͋ͅǍ̔
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/Text/Zalgo.hs b/Text/Zalgo.hs
new file mode 100644
--- /dev/null
+++ b/Text/Zalgo.hs
@@ -0,0 +1,215 @@
+-- | Provides facilities for generating a wide range of glitched/creepy
+--   text through horrifying abuse of diacritics.
+module Text.Zalgo
+  ( -- * Pure interface
+    zalgo, zalgoWith, gradualZalgo, unZalgo
+
+    -- * Effectful interface
+  , zalgoIO, zalgoIOWith, gradualZalgoIOWith
+
+    -- * Printing functions
+  , printZalgo, printZalgoWith, printGradualZalgo
+
+    -- * Configuration
+  , ZalgoSettings
+  , maxHeightAt, varianceAt, overlayProbabilityAt
+  , defaultZalgoSettings
+  ) where
+import Data.Array (Array, listArray, bounds, elems, (!))
+import Data.Char (chr)
+import Data.List (foldl')
+import System.Random (RandomGen, StdGen, newStdGen, randomR, randomRs, split)
+
+-- TODO: sporadically zalgo a text using Perlin noise
+
+data ZalgoSettings = ZalgoSettings
+  { -- | Maximum number of diacritics above or below a character at the
+    --   given position of the input string.
+    --
+    --   Default: const 10
+    maxHeightAt :: Int -> Int
+
+    -- | Maximum random variance in height, as a fraction of 'maxHeight', at
+    --   the given position of the input string.
+    --
+    --   Default: const 1
+  , varianceAt :: Int -> Double
+
+    -- | Probability of generating an overlay character at the given position
+    --   of the input string.
+    --
+    --   Default: const 0.4
+  , overlayProbabilityAt :: Int -> Double
+  }
+
+-- | The default zalgo settings. Creepy yet readable.
+defaultZalgoSettings :: ZalgoSettings
+defaultZalgoSettings = ZalgoSettings
+  { maxHeightAt          = const 10
+  , varianceAt           = const 1
+  , overlayProbabilityAt = const 0.4
+  }
+
+minHeightAt :: ZalgoSettings -> Int -> Int
+minHeightAt cfg n = floor $ maxH - maxH * varianceAt cfg n
+  where
+    maxH = fromIntegral (maxHeightAt cfg n)
+
+-- | Combining diacritics above.
+over :: Array Int Char
+over = listArray (0, length list-1) list
+  where
+    list = map chr $ concat
+      [ [768 .. 789]
+      , [829 .. 836]
+      , [842 .. 844]
+      , [848 .. 850]
+      , [867 .. 879] -- latin letters
+      , [794, 795, 836, 838, 855, 856, 859, 861, 862, 864, 865]
+      ]
+
+-- | Overlaid diacritics.
+overlay :: Array Int Char
+overlay = listArray (0, length list-1) list
+  where
+    list = map chr [820 .. 824]
+
+-- | Combining diacritics below.
+under :: Array Int Char
+under = listArray (0, length list-1) list
+  where
+    list = map chr $ concat
+      [ [x | x <- [790 .. 819], not $ x `elem` [794, 795]]
+      , [825 .. 828]
+      , [839 .. 841]
+      , [851 .. 854]
+      , [837, 845, 846, 857, 858, 860, 863]
+      ]
+
+-- | Choose n characters from the given list, where n is chosen at random
+--   in the given interval.
+combiners :: RandomGen g => Array Int Char -> (Int, Int) -> g -> (g, [Char])
+combiners source numRange g =
+    (g1, take numMarks $ map (source !) indices)
+  where
+    (g0, g1) = split g
+    (numMarks, g0') = randomR numRange g0
+    indices = randomRs (bounds source) g0'
+
+-- | Combine a character with over, under and overlay characters.
+--   At most one overlay character is chosen, with probability @overlayProb@.
+--   The numbers of top and bottom characters are drawn from the given interval.
+combineAll :: RandomGen g => Double -> (Int, Int) -> Char -> g -> (g, String)
+combineAll overlayProb numRange c gen
+  | o <= overlayProb =
+    case marks of
+      (g, marks') -> fmap ((c:marks')++) (combiners overlay (1, 1) g)
+  | otherwise =
+    fmap (c:) marks
+  where
+    (o, gen') = randomR (0, 1) gen
+    marks = foldl' f (gen', "") [over, under]
+    f (g, s') src = fmap (s'++) (combiners src numRange g)
+
+
+-- | Exorcise Zalgo from the given string.
+unZalgo :: String -> String
+unZalgo = filter (not . (`elem` concat [elems over, elems under, elems overlay]))
+
+-- | Zalgo the given text, using the given algorithm settings and generator.
+zalgoWith :: RandomGen g => ZalgoSettings -> String -> g -> (g, String)
+zalgoWith cfg s g0 = fmap (concat . reverse) $ snd $ foldl' f (0, (g0, [])) s
+  where
+    f (n, (g, s')) c = (n+1, fmap (:s') (combineAll o (lo, hi) c g))
+      where
+        hi = maxHeightAt cfg n
+        lo = minHeightAt cfg n
+        o = overlayProbabilityAt cfg n
+
+-- | Zalgo the given text using the default zalgo settings and the given
+--   generator.
+zalgo :: RandomGen g => String -> g -> (g, String)
+zalgo = zalgoWith defaultZalgoSettings
+
+-- | Zalgo the given text with the given settings, but scale the maximum
+--   height and overlay probability
+--   according to the fraction of the string processed so far.
+--
+--   For example, to only zalgo the last 25% of the string @s@, using settings
+--   @cfg@ and generator @g@, one would do:
+--
+--   > gradualZalgoWith cfg (\f -> if f >= 0.75 then 1 else 0)
+--
+--   To start scaling the string after 75%, and then linearly increase the
+--   output's zalgo level until the final character is zalgo'd using the
+--   base settings:
+--
+--   > gradualZalgoWith cfg (\f -> if f >= 0.75 then (f-0.75)*4 else 0)
+gradualZalgoWith :: ZalgoSettings      -- ^ Base settings for this run.
+                 -> (Double -> Double) -- ^ Scale variance and overlay
+                                       --   probability by this function, whose
+                                       --   input is the fraction of the input
+                                       --   string processed so far.
+                 -> String
+                 -> StdGen
+                 -> (StdGen, String)
+gradualZalgoWith cfg f s g = zalgoWith cfg' s g
+  where
+    len = fromIntegral $ length s
+    scale g n = f (fromIntegral n/len) * g n
+    cfg' = cfg
+      { maxHeightAt = round . scale (fromIntegral . maxHeightAt cfg)
+      , overlayProbabilityAt = scale (overlayProbabilityAt cfg)
+      }
+
+-- | Gradually zalgo the given string, starting from the given threshold and
+--   linearly scaling towards the default zalgo settings.
+gradualZalgo :: Double -> String -> StdGen -> (StdGen, String)
+gradualZalgo from = gradualZalgoWith defaultZalgoSettings f
+  where
+    f x | x >= from = (x-from)*(1/(1-from))
+        | otherwise = 0
+
+
+-- | Zalgo the given text with the given settings, using a fresh
+--   standard generator.
+zalgoIOWith :: ZalgoSettings -> String -> IO String
+zalgoIOWith cfg s = do
+  g <- newStdGen
+  return $ snd $ zalgoWith cfg s g
+
+-- | Zalgo the given text using the standard settings and a fresh generator.
+zalgoIO :: String -> IO String
+zalgoIO = zalgoIOWith defaultZalgoSettings
+
+-- | Zalgo the given text using a fresh random generator,
+--   starting after the given fraction of the input string, from there on
+--   scaling the zalgo factor linearly towards the given settings.
+gradualZalgoIOWith :: ZalgoSettings -> Double -> String -> IO String
+gradualZalgoIOWith cfg from s = do
+  g <- newStdGen
+  return $ snd $ gradualZalgoWith cfg f s g
+  where
+    f x | x >= from = (x-from)*(1/(1-from))
+        | otherwise = 0
+
+-- | Zalgo the given text using a fresh random generator,
+--   starting after the given fraction of the input string, from there on
+--   scaling the zalgo factor linearly towards the default settings.
+gradualZalgoIO :: Double -> String -> IO String
+gradualZalgoIO = gradualZalgoIOWith defaultZalgoSettings
+
+
+-- | Print zalgo'd text using the given settings and a fresh
+--   random generator.
+printZalgoWith :: ZalgoSettings -> String -> IO ()
+printZalgoWith cfg s = zalgoIOWith cfg s >>= putStrLn
+
+-- | Print zalgo'd text using the default settings and a fresh default generator.
+printZalgo :: String -> IO ()
+printZalgo = printZalgoWith defaultZalgoSettings
+
+-- | Gradually zalgo and print the given text starting at the given threshold.
+--   Uses default settings and a fresh system default generator.
+printGradualZalgo :: Double -> String -> IO ()
+printGradualZalgo from s = gradualZalgoIO from s >>= putStrLn
diff --git a/acme-zalgo.cabal b/acme-zalgo.cabal
new file mode 100644
--- /dev/null
+++ b/acme-zalgo.cabal
@@ -0,0 +1,25 @@
+name:                acme-zalgo
+version:             0.1.0.0
+synopsis:            A somewhat flexible Zalgo̐ te̳͜x̥̖̉̓͞t̍̌̔ ̀̃t̴̢̞̜͓̝r̶̬̆̂̒͟á̧̡͎͔̯̰̕n̹̾̓ͬͦ̍͘ṡ̢͓͉ͮ͆l̠̖̹̗̳̖̽̌ͤ͞a͚̭͙̹̲ͭͩt͈͐o̢̭͇͍̟͐ͬ̾ͪ͜r͇.̸̅ͭ̐̀̊ͨ͛
+description:         "Zalgo text" refers to the glitchy-looking and somewhat eerie text produced by combining Unicode diacritics into an unholy mess. It is sometimes used to to indicate the presence ǒ̲f͓ ͡s͕ome̡͆t̳h̫i̵̤nͮg ͍sͩi̱n̦i̜̕s̺̼͛t̓er̬̃̾ ̛̙̀͟ą̈́n̤͜d ̖m̛̝̽i͖̎n͕͈d̯̀ͥ-ṱ͟wi̜sͤ̕t̡͎͚̓ï̵̆n̢͐ͮ͛g̽ͮ̋ ̗͜e̴̙͊̓͘n̶̔̚ó̻͈u̯ͭg̦h͂ ̪̠͖͑̐͠t̳͓̏̐ͣo̦̠͠ ͓̥b̘̫͛̕r̰̳͞e͇͖̘ä̢̳̤̈́͐k̅ ̛͔̏͝͡r̺̕͜e̶̢̪͓͖á̐́l̺̺̼̇i̷̥͓ͩt̹y,̬̍ ̴̰̰̹̫ͭ̈č̸͓̉ͅã̴͖͕̹͂̿̚͜ǘ̋̕͞ş͚̠͛͟͟iͭ̊ͦͤͯn̷̪̤͔̘̈̃̔ġ̶̮ͮͮ̊̚ ̵̨̙ͩ̀͊͞͡s͌̉e͆v̸͚͗̅̅e͐̓r͑e̷̠͈͡ ̼̰͔̋ͨ̐͆ͧ͟͟c̺̿̈́̐̀͡o͚ͮͮ͗͆͡r̷̢͈̫ͧ̄r͓u̹͙̻͍p͒ͦͮt̻̀̽̚ỉ̢̲̘͚̹ͧ̅ͤȏ̵͙͕ͩ̊͘͝n̵͖̍ ̵̧̩̤͎̦i̱͂̿͝n͉͋͗ ͓̣̖̣aͫ̎́n̵̲̜̯ͭ͗̎̐̀ͪy͑ͮ͗ͥ͡ ̨͓̎̽ͫ̏͊͝ņ̸͔̞̘͖̠̓̀ͫe͓͟a̵̢̰̱ͨŗ̮͕̦̪͞b̴̝̝ͥȳ̦̼͓ͤ̈́̀̌ ̨̼̠̗͟t̙͉̹͍ͪ̄̈͟ͅé̹̗̳ͤ͗̈̽͟͝x̥͙͍̬̥ͅt͍̰̪̔ͯ,ͯͧ̂̐̚̕͡ ̱̜́̊͘͜ǎ̧̟̤͋ͨn̮̹̆̎͗̾͟͡i̮̘̐ͯ͗͌̉͗͘m̛̰̤̩ͦ̏̓ͣ͜ͅ ä̴̦̫́͂̿̌ͦ͝ͅ ̺̲̣̼̦̈͜l̨̬̫̉̋͜͡ ̨̗͈̐ͣ̓́ͣ̃́s̲̞̣͓͗ ̢͉̫͚͍͠ó̠ͩ̽ͯ̍̃͘ŗ̜̈́ͨͨ̾̏̊̔͠ ̱͈ͯ̿̀̂̀̌̈͜ų͙͍͍̹̞̜͗̌͡ṅ̶͖̠̄̆̚ ͍̪̬̮̂̌̑̎ͪ̾f̡͎̮͊͂͐͒̊̿͡ ͚̩̯̼̠̀̃ͨ͘͞ȯ̭̖̻͜ͅr̡̞̟ͫ͜ț̴͎͔̤̒ͥ̌ ͆́͘̚͡û̞̪̮ͧ̌̑̓̎̚ ̻̰̗̠̏́ͪ͡n̠̺ͣ́ͨͥ͋ ̵́͐̒ͧͤ̈́̉̆̚a̭̽̀̎̈͐̌̐ͦ̓t̡̛̔̄̀͋ͩ͂́͝e͊ ̨̯̖̖͕͇̚ş̷̼̥̤͈̱̟ ̙̦̪̙͔ͤ͘͟o̶̻͉̯̟u̵ ̻̬̥ͬ̑ͫͮ͡͠͞H̥̲̣̔̌̚ ̡̬̞̤͈̱̠̬ͪͦE̮̳͉̖̟͑ͬͦ́ ̧̨̹̜C͉̩͓͉̀ͯ̈́̄ͩ͂ ̸͓̳ͤO̡̠̝̬̭̜͙̔̾̈́ ̷̩̻̭̣̲ͦ͊͆M̴͓͊ͬ͌̂̈́͑ ̧̲̬̹̻͖ͭÉ̡̛̬̗͙̜͛ ̮̠̐ͣͫ̉͠͝Ş̥̮̈̉ͧ͂ͩ̀͡!̢͚ͥͪ̿ͯͤͫ͌̀
+license:             MIT
+license-file:        LICENSE
+author:              O̶ ̽̒̌̋͂͌͆͐͛͝N̨̨̨̟̘̭̘̮͒͘ ̛̛̻̞ͥͦ͂̊̅͑Lͯͯ́̆̎͊̐̋͛̊ ̶̰̘͍͎ͬͪ̌̊̕Y͕͙͔̟̠̠̯͈̌̽ ̹̜͔̟̪̮̌̈́̓͠M̹̣̓͘͝͞ ̨͎̲͙̼O̧̹̓̉͊͋̇ͨ̄͞ ̵̡̦̦̘͐̑͠N͗͑͑͋͂̿ͭ̑̈̃ ̝̭̳ͭ̃͆͛I͚̬͖̟̪͓̯ͬͪ͜ ̬̗͎͎̮̺̑̈́̓͡Ḵ̷̀ͯ́ͮ͐ͦ͝͞ ̭͓͙͙ͬ̈͋ͅǍ̔
+maintainer:          anton@ekblad.cc
+category:            Acme
+build-type:          Simple
+cabal-version:       >=1.10
+
+source-repository head
+  type:       git
+  location:   https://github.com/valderman/zalgo.git
+
+library
+  exposed-modules:
+    Text.Zalgo
+  build-depends:
+    base   >=4.6 && <4.11,
+    array  >=0.5 && <0.6,
+    random >=1.1 && <1.2
+  default-language:
+    Haskell2010
