diff --git a/CONTRIBUTORS b/CONTRIBUTORS new file mode 100644 index 0000000..3008090 --- /dev/null +++ b/CONTRIBUTORS @@ -0,0 +1,16 @@ +This is the official list of people who have contributed code to the Phrozn project. + +Most up to date list is located here: https://github.com/farazdagi/phrozn/contributors + +Names should be added to this file like so: + Name + +Please keep the list sorted. + +Chris Smith +Jonathan Van Belle +Kazusuke Sasezaki +Osman Üngür +Ralph Schindler +Timo Haberkern + diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..bece888 --- /dev/null +++ b/NOTICE @@ -0,0 +1,6 @@ +Phrozn - Static site generator for PHP +Copyright 2011 Victor Farazdagi + +Portions of this software were developed and contributed by: + +- Osman Üngür (Phrozn Internal Web-Server) diff --git a/Phrozn/Autoloader.php b/Phrozn/Autoloader.php index b704653..002c51b 100644 --- a/Phrozn/Autoloader.php +++ b/Phrozn/Autoloader.php @@ -1,23 +1,20 @@ getFiles() as $entry) { if ($entry['typeflag'] != 5) { // ignore dirs @@ -130,7 +127,7 @@ public function removeFrom($path) $path . DIRECTORY_SEPARATOR . $entry['filename']); if (false === @unlink($filepath)) { throw new \Exception( - 'Error removing file "%s": %s', + 'Error removing file "%s": %s', $entry['filename'], \error_get_last()); } } @@ -150,8 +147,8 @@ public function removeFrom($path) public function getInfo($option = null) { if (null !== $this->bundleData) { - return $option - ? $this->bundleData[$option] + return $option + ? $this->bundleData[$option] : $this->bundleData; } @@ -172,8 +169,8 @@ public function getInfo($option = null) throw new \Exception(sprintf('Bundle "%s" not found..', $key)); } - return $option - ? $this->bundleData[$option] + return $option + ? $this->bundleData[$option] : $this->bundleData; } diff --git a/Phrozn/Bundle/Service.php b/Phrozn/Bundle/Service.php index 1343a36..463ef05 100644 --- a/Phrozn/Bundle/Service.php +++ b/Phrozn/Bundle/Service.php @@ -1,23 +1,20 @@ isInstalled($bundle['id'])) { diff --git a/Phrozn/Config.php b/Phrozn/Config.php index 0fa9ac0..b0e3f9e 100644 --- a/Phrozn/Config.php +++ b/Phrozn/Config.php @@ -1,23 +1,20 @@ + *
      *                  text      text            background
      *      ------------------------------------------------
      *      %k %k %0    black     dark grey       black
@@ -129,13 +126,13 @@ public static function convert($string, $colored = true)
 
     /**
      * Escapes % so they don't get interpreted as color codes
-     * 
+     *
      * @param string $string String to escape
      *
      * @access public
      * @return string
      */
-    public static function escape($string) 
+    public static function escape($string)
     {
         return self::getInstance()
             ->getConsoleColorer()
@@ -150,7 +147,7 @@ public static function escape($string)
      * @acess public
      * @return string
      */
-    public static function strip($string) 
+    public static function strip($string)
     {
         return self::getInstance()
             ->getConsoleColorer()
diff --git a/Phrozn/Outputter/DefaultOutputter.php b/Phrozn/Outputter/DefaultOutputter.php
index 6581b77..db64615 100644
--- a/Phrozn/Outputter/DefaultOutputter.php
+++ b/Phrozn/Outputter/DefaultOutputter.php
@@ -1,23 +1,20 @@
 setDao($dao);
 
-        $this->init(); // allow sub-classes to initialize 
+        $this->init(); // allow sub-classes to initialize
     }
 
     /**
@@ -170,7 +167,7 @@ public function getValues()
     }
 
     /**
-     * Set property value 
+     * Set property value
      *
      * @param string $name Property name
      * @param mixed $value Property value
diff --git a/Phrozn/Registry/Container/Bundles.php b/Phrozn/Registry/Container/Bundles.php
index 73e7dd8..b9bf665 100644
--- a/Phrozn/Registry/Container/Bundles.php
+++ b/Phrozn/Registry/Container/Bundles.php
@@ -1,24 +1,21 @@
 save();
         return $this;
     }
-    
+
     /**
      * Mark bundle as uninstalled (clean from registry)
      *
diff --git a/Phrozn/Registry/Dao.php b/Phrozn/Registry/Dao.php
index 14800c0..ec99da4 100644
--- a/Phrozn/Registry/Dao.php
+++ b/Phrozn/Registry/Dao.php
@@ -1,23 +1,20 @@
 isAbsolute($path)) { // not an absolute path
             $path = \getcwd() . '/./' . $path;
         }
-        
+
         if ($realpath) {
             $path = realpath($path);
         }
diff --git a/Phrozn/Runner/CommandLine/Callback/Bundle.php b/Phrozn/Runner/CommandLine/Callback/Bundle.php
index ae02426..6e530ae 100644
--- a/Phrozn/Runner/CommandLine/Callback/Bundle.php
+++ b/Phrozn/Runner/CommandLine/Callback/Bundle.php
@@ -1,23 +1,20 @@
 addRow(array(
-                $this->service->getRegistryContainer()->isInstalled($bundle['id']) ? 'i' : 'p', 
-                $bundle['id'], 
+                $this->service->getRegistryContainer()->isInstalled($bundle['id']) ? 'i' : 'p',
+                $bundle['id'],
                 $bundle['version'],
                 $bundle['author'],
                 $bundle['description'],
@@ -153,8 +150,8 @@ private function execList()
     }
 
     /**
-     * Get info about specific bundle 
-     * 
+     * Get info about specific bundle
+     *
      * @return void
      */
     private function execInfo()
@@ -176,8 +173,8 @@ private function execInfo()
     }
 
     /**
-     * Apply bundle 
-     * 
+     * Apply bundle
+     *
      * @return void
      */
     private function execApply()
@@ -217,8 +214,8 @@ private function execApply()
     }
 
     /**
-     * Clobber bundle 
-     * 
+     * Clobber bundle
+     *
      * @return void
      */
     private function execClobber()
@@ -242,7 +239,7 @@ private function execClobber()
             }
         }
 
-        $this->out( 
+        $this->out(
             "\nBundle files are to be removed.\n" .
             "This operation %rCAN NOT%n be undone.\n");
         if ($this->readLine() === 'yes') {
@@ -263,7 +260,7 @@ private function execClobber()
      */
     private function getTypeParam()
     {
-        $options = $this->getCommand()->options; 
+        $options = $this->getCommand()->options;
         $type = \Phrozn\Bundle::TYPE_ALL;
         if ($options['installed']) {
             $type = \Phrozn\Bundle::TYPE_INSTALLED;
diff --git a/Phrozn/Runner/CommandLine/Callback/Clobber.php b/Phrozn/Runner/CommandLine/Callback/Clobber.php
index 5ef701d..d311598 100644
--- a/Phrozn/Runner/CommandLine/Callback/Clobber.php
+++ b/Phrozn/Runner/CommandLine/Callback/Clobber.php
@@ -1,23 +1,20 @@
 out($this->getHeader());
         $this->out("Purging project data..");
         $this->out("\nLocated project folder: {$path}");
-        $this->out( 
+        $this->out(
             "Project folder is to be removed.\n" .
             "This operation %rCAN NOT%n be undone.\n");
 
diff --git a/Phrozn/Runner/CommandLine/Callback/Help.php b/Phrozn/Runner/CommandLine/Callback/Help.php
index c1c56ad..4d52380 100644
--- a/Phrozn/Runner/CommandLine/Callback/Help.php
+++ b/Phrozn/Runner/CommandLine/Callback/Help.php
@@ -1,23 +1,20 @@
 combine($topic, $this->getParseResult()->command->options['verbose'])) {
             return $help;
-        } 
+        }
         return "%rHelp topic '$topic' not found..%n\n";
     }
 
     private function getUsageHelp()
     {
         $commands = CommandLine\Commands::getInstance();
-        
+
         $out = "usage: %bphrozn%n %g%n [options] [args]\n\n";
         $out .= "Type 'phrozn help ' for help on a specific command.\n";
         $out .= "Type 'phrozn ? help' for help on using help.\n";
diff --git a/Phrozn/Runner/CommandLine/Callback/Init.php b/Phrozn/Runner/CommandLine/Callback/Init.php
index eb76c68..63a5178 100644
--- a/Phrozn/Runner/CommandLine/Callback/Init.php
+++ b/Phrozn/Runner/CommandLine/Callback/Init.php
@@ -1,23 +1,20 @@
 read()) {
@@ -127,7 +124,7 @@ private function copy($source, $dest, $callback)
             }
             $this->copy("$source/$entry", "$dest/$entry", $callback);
         }
-    
+
         // Clean up
         $dir->close();
         return true;
diff --git a/Phrozn/Runner/CommandLine/Callback/Up.php b/Phrozn/Runner/CommandLine/Callback/Up.php
index 1f31624..501bbb0 100644
--- a/Phrozn/Runner/CommandLine/Callback/Up.php
+++ b/Phrozn/Runner/CommandLine/Callback/Up.php
@@ -1,23 +1,20 @@
 path = $path;
         self::$instance->rewind(); // make sure we reset instance
@@ -107,11 +104,11 @@ public function valid()
         $it = $this->getIterator();
 
         // skip directories and file !*.yml
-        while ($it->valid() 
+        while ($it->valid()
             && (
                 $it->isDot()
                 || $it->isLink()
-                || $it->isFile() === false 
+                || $it->isFile() === false
                 || $it->getBasename('.yml') === $it->getFilename() // only *.yml files
             )
         ) {
diff --git a/Phrozn/Runner/CommandLine/Parser.php b/Phrozn/Runner/CommandLine/Parser.php
index da56a0a..7b54392 100644
--- a/Phrozn/Runner/CommandLine/Parser.php
+++ b/Phrozn/Runner/CommandLine/Parser.php
@@ -1,23 +1,20 @@
 getOutputter()
             ->stdout($prompt);
-        $out = fgets($this->getHandle()); 
+        $out = fgets($this->getHandle());
         $this->getOutputter()->stdout("\n");
         return rtrim($out);
     }
diff --git a/Phrozn/Site.php b/Phrozn/Site.php
index 7ca9ba3..c3813c8 100644
--- a/Phrozn/Site.php
+++ b/Phrozn/Site.php
@@ -1,23 +1,20 @@
 getInputDir(), '/');
         if (is_dir($dir . '/.phrozn')) {
             $dir .= '/.phrozn/';
-        } 
+        }
 
         // see if we have entries folder present
         if (!is_dir($dir . '/entries')) {
diff --git a/Phrozn/Site/DefaultSite.php b/Phrozn/Site/DefaultSite.php
index 8e0c31f..c742e31 100644
--- a/Phrozn/Site/DefaultSite.php
+++ b/Phrozn/Site/DefaultSite.php
@@ -1,23 +1,20 @@
 getQueue() as $view) {
             $inputFile = str_replace(getcwd(), '.', $view->getInputFile());
             $outputFile = str_replace(getcwd(), '.', $view->getOutputFile());
@@ -100,8 +97,8 @@ private function processMedia()
 
                 $path = $it->getSubPath();
 
-                $outputFile = $outputDir . '/media/' . $path 
-                            . (!empty($path) ? '/' : '') 
+                $outputFile = $outputDir . '/media/' . $path
+                            . (!empty($path) ? '/' : '')
                             . basename($inputFile);
 
                 // copy media files
diff --git a/Phrozn/Site/View.php b/Phrozn/Site/View.php
index 4bb70df..8df0a62 100644
--- a/Phrozn/Site/View.php
+++ b/Phrozn/Site/View.php
@@ -1,23 +1,20 @@
 getView()->getInputFile());
-        return $info['dirname'] 
-            . DIRECTORY_SEPARATOR 
+        return $info['dirname']
+            . DIRECTORY_SEPARATOR
             . ($info['filename']?:$info['basename']); // allow dot files
     }
 
diff --git a/Phrozn/Site/View/OutputPath/Entry.php b/Phrozn/Site/View/OutputPath/Entry.php
index 50cd3d5..cfa0143 100644
--- a/Phrozn/Site/View/OutputPath/Entry.php
+++ b/Phrozn/Site/View/OutputPath/Entry.php
@@ -1,23 +1,20 @@
 getView()->getOutputDir(), '/')
                 . '/'
                 . ltrim($this->getRelativeFile('entries', false), '/') . '.html';
-        } 
+        }
 
         $class = 'Phrozn\\Site\\View\\OutputPath\\Entry\\' . ucfirst($permalink);
         if (!class_exists($class)) {
diff --git a/Phrozn/Site/View/OutputPath/Entry/Parametrized.php b/Phrozn/Site/View/OutputPath/Entry/Parametrized.php
index efad360..94a786c 100644
--- a/Phrozn/Site/View/OutputPath/Entry/Parametrized.php
+++ b/Phrozn/Site/View/OutputPath/Entry/Parametrized.php
@@ -1,23 +1,20 @@
 getView()->getOutputDir(), '/') . '/' 
+        $path = rtrim($this->getView()->getOutputDir(), '/') . '/'
               . ltrim($path, '/');
 
         if (substr($path, -1) === '/') {
@@ -79,7 +76,7 @@ private function normalize($param, $space = '-')
         if (function_exists('iconv')) {
             $param = @iconv('utf-8', 'us-ascii//TRANSLIT', $param);
         }
-        $param = preg_replace('/[^a-zA-Z0-9 -]/', '', $param); 
+        $param = preg_replace('/[^a-zA-Z0-9 -]/', '', $param);
         $param = strtolower($param);
         $param = preg_replace('/[\s-]+/', $space, $param);
 
diff --git a/Phrozn/Site/View/OutputPath/Plain.php b/Phrozn/Site/View/OutputPath/Plain.php
index 81aad24..d67bee7 100644
--- a/Phrozn/Site/View/OutputPath/Plain.php
+++ b/Phrozn/Site/View/OutputPath/Plain.php
@@ -1,23 +1,20 @@
 getView()->getOutputDir(), '/')
                 . '/'
                 . ltrim($this->getRelativeFile('entries', false), '/');
-        } 
+        }
 
         $class = 'Phrozn\\Site\\View\\OutputPath\\Entry\\' . ucfirst($permalink);
         if (!class_exists($class)) {
diff --git a/Phrozn/Site/View/OutputPath/Script.php b/Phrozn/Site/View/OutputPath/Script.php
index 006f8c0..8cf6e67 100644
--- a/Phrozn/Site/View/OutputPath/Script.php
+++ b/Phrozn/Site/View/OutputPath/Script.php
@@ -1,23 +1,20 @@
 getView()->getOutputDir(), '/') 
+        return rtrim($this->getView()->getOutputDir(), '/')
              . '/'
              . ltrim($this->getRelativeFile('scripts'), '/') . '.js';
     }
diff --git a/Phrozn/Site/View/OutputPath/Style.php b/Phrozn/Site/View/OutputPath/Style.php
index 7164048..1ee931f 100644
--- a/Phrozn/Site/View/OutputPath/Style.php
+++ b/Phrozn/Site/View/OutputPath/Style.php
@@ -1,23 +1,20 @@
 getView()->getOutputDir(), '/') 
+        return rtrim($this->getView()->getOutputDir(), '/')
              . '/'
              . ltrim($this->getRelativeFile('styles'), '/') . '.css';
     }
diff --git a/Phrozn/Site/View/Plain.php b/Phrozn/Site/View/Plain.php
index 14af52a..0af4ac9 100644
--- a/Phrozn/Site/View/Plain.php
+++ b/Phrozn/Site/View/Plain.php
@@ -1,23 +1,20 @@
 
+ * Copyright 2011, Leaf Corcoran 
  * Licensed under MIT or GPLv3, see LICENSE
  */
 
@@ -15,7 +15,7 @@
  * The less compiler and parser.
  *
  * Converting LESS to CSS is a two stage process. First the incoming document
- * must be parsed. Parsing creates a tree in memory that represents the 
+ * must be parsed. Parsing creates a tree in memory that represents the
  * structure of the document. Then, the tree of the document is recursively
  * compiled into the CSS text. The compile step has an implicit step called
  * reduction, where values are brought to their lowest form before being
@@ -33,1717 +33,2207 @@
  *
  */
 class lessc {
-	protected $buffer;
-	protected $count;
-	protected $line;
-	protected $expandStack;
-
-	public $indentLevel;
-	public $indentChar = '  ';
-
-	protected $env = null;
-
-	protected $allParsedFiles = array();
-
-	public $vPrefix = '@'; // prefix of abstract properties
-	public $mPrefix = '$'; // prefix of abstract blocks
-	public $imPrefix = '!'; // special character to add !important
-	public $selfSelector = '&';
-
-	static protected $precedence = array(
-		'+' => 0,
-		'-' => 0,
-		'*' => 1,
-		'/' => 1,
-		'%' => 1,
-	);
-	static protected $operatorString; // regex string to match any of the operators
-
-	// types that have delayed computation
-	static protected $dtypes = array('expression', 'variable',
-		'function', 'negative', 'list');
-
-	/**
-	 * @link http://www.w3.org/TR/css3-values/
-	 */
-	static protected $units=array(
-		'em', 'ex', 'px', 'gd', 'rem', 'vw', 'vh', 'vm', 'ch', // Relative length units
-		'in', 'cm', 'mm', 'pt', 'pc', // Absolute length units
-		'%', // Percentages
-		'deg', 'grad', 'rad', 'turn', // Angles
-		'ms', 's', // Times
-		'Hz', 'kHz', //Frequencies
-	);
-    
-	public $importDisabled = false;
-	public $importDir = '';
-
-	/**
-	 * Parse a single chunk off the head of the buffer and place it.
-	 * @return false when the buffer is empty, or there is an error
-	 *
-	 * This functions is called repeatedly until the entire document is
-	 * parsed.
-	 *
-	 * This parser is most similar to a recursive descent parser. Single
-	 * functions represent discrete grammatical rules for the language, and
-	 * they are able to capture the text that represents those rules.
-	 *
-	 * Consider the function lessc::keyword(). (all parse functions are
-	 * structured the same)
-	 *
-	 * The function takes a single reference argument. When calling the the
-	 * function it will attempt to match a keyword on the head of the buffer.
-	 * If it is successful, it will place the keyword in the referenced
-	 * argument, advance the position in the buffer, and return true. If it
-	 * fails then it won't advance the buffer and it will return false.
-	 *
-	 * All of these parse functions are powered by lessc::match(), which behaves
-	 * the same way, but takes a literal regular expression. Sometimes it is
-	 * more convenient to use match instead of creating a new function.
-	 *
-	 * Because of the format of the functions, to parse an entire string of
-	 * grammatical rules, you can chain them together using &&.
-	 *
-	 * But, if some of the rules in the chain succeed before one fails, then
-	 * then buffer position will be left at an invalid state. In order to 
-	 * avoid this, lessc::seek() is used to remember and set buffer positions.
-	 *
-	 * Before doing a chain, use $s = $this->seek() to remember the current
-	 * position into $s. Then if a chain fails, use $this->seek($s) to 
-	 * go back where we started.
-	 *
-	 * When something is successfully parsed, depending on what it is, it is
-	 * placed in the document's tree by being put in the recursive structure
-	 * lessc::$env. $env is an anonymous object with a $store and a $parent. 
-	 * $store is an associative array of all the objects held, and $parent is
-	 * the $env object one level above.
-	 *
-	 * When parsing is complete on a valid document, there is only one $env 
-	 * left and the $store member contains the block of the entire structure
-	 * of the document. This block can then be passed to compile block to 
-	 * generate the CSS.
-	 *
-	 */
-	function parseChunk() {
-		if (empty($this->buffer)) return false;
-		$s = $this->seek();
-
-		// a property
-		if ($this->keyword($key) && $this->assign() && $this->propertyValue($value) && $this->end()) {
-			// look for important prefix
-			if ($key{0} == $this->imPrefix && strlen($key) > 1) {
-				$key = substr($key, 1);
-				if ($value[0] == 'list' && $value[1] == ' ') {
-					$value[2][] = array('keyword', '!important');
-				} else {
-					$value = array('list', ' ', array($value, array('keyword', '!important')));
-				}
-			}
-			$this->append($key, $value);
-			return true;
-		} else {
-			$this->seek($s);
-		}
-
-		// look for special css @ directives
-		if ($this->env->parent == null && $this->literal('@', false)) {
-			$this->count--; // give back @
-
-			// a font-face block
-			if ($this->literal('@font-face') && $this->literal('{')) {
-				$this->push(array( '__special' => 'font-face',));
-				return true;
-			} else {
-				$this->seek($s);
-			}
-
-			// charset
-			if ($this->literal('@charset') && $this->propertyValue($value) && $this->end()) {
-				$this->setLiteral('@charset '.$this->compileValue($value).';');
-				return true;
-			} else {
-				$this->seek($s);
-			}
-
-			// media
-			if ($this->literal('@media') && $this->mediaTypes($types, $rest) && $this->literal('{')) {
-				$this->push(array(
-					'__special' => 'media',
-					'__media' => array($types, $rest),
-				));
-				return true;
-			} else {
-				$this->seek($s);
-			}
-			
-			// css animations
-			if ($this->match('(@(-[a-z]+-)?keyframes)', $m) && $this->propertyValue($value) && $this->literal('{')) {
-				$this->push(array(
-					'__special' => 'keyframes',
-					'__keyframes' => array($m[0], $value),
-				));
-				return true;
-			} else {
-				$this->seek($s);
-			}
-		}
-		
-		// see if can accept animation pseudo classes
-		if ($this->getRaw('__special') == 'keyframes') {
-			if ($this->match("(to|from|[0-9]+%)", $m) && $this->literal('{')) {
-				$this->push(array(
-					'__tags' => array($m[1])
-				));
-				return true;
-			} else {
-				$this->seek($s);
-			}
-		}
-
-		// setting variable
-		if ($this->variable($name) && $this->assign() && $this->propertyValue($value) && $this->end()) {
-			$this->append($this->vPrefix.$name, $value);
-			return true;
-		} else {
-			$this->seek($s);
-		}
-
-		// opening abstract block
-		if ($this->tag($tag, true) && $this->argumentDef($args) && $this->literal('{')) {
-			// move out of variable namespace
-			if ($tag{0} == $this->vPrefix) $tag[0] = $this->mPrefix;
-
-			$this->push(array(
-				'__tags' => array($tag)
-			));
-
-			if (isset($args)) $this->set('__args', $args);
-
-			return true;
-		} else {
-			$this->seek($s);
-		}
-
-		// opening css block
-		if ($this->tags($tags) && $this->literal('{')) {
-			//  move @ tags out of variable namespace
-			foreach ($tags as &$tag) {
-				if ($tag{0} == $this->vPrefix) $tag[0] = $this->mPrefix;
-			}
-
-			$this->push(array(
-				'__tags' => $tags
-			));
-
-			return true;
-		} else {
-			$this->seek($s);
-		}
-
-		// closing block
-		if ($this->literal('}')) {
-			try {
-				$block = $this->pop();
-			} catch (exception $e) {
-				$this->seek($s);
-				$this->throwParseError($e->getMessage());
-			}
-
-			if (isset($block['__special'])) {
-				$this->set('__special'.count($this->env->store), $block);
-				return true;
-			}
-
-			// make the block(s) available in the new current scope
-			$merge_env = array();
-			foreach ($block['__tags'] as $t) $merge_env[$t] = $block;
-			$this->merge($merge_env);
-
-			return true;
-		} 
-		
-		// import statement
-		if ($this->import($url, $media)) {
-			if ($this->importDisabled) {
-				$this->setLiteral("/* import is disabled */");
-				return true;
-			}
-
-			foreach ((array)$this->importDir as $dir) {
-				$full = $dir.(substr($dir, -1) != '/' ? '/' : '').$url;
-				if ($this->fileExists($file = $full.'.less') || $this->fileExists($file = $full)) {
-					$this->addParsedFile($file);
-					$loaded = ltrim($this->removeComments(file_get_contents($file).";"));
-
-					$this->buffer = substr($this->buffer, 0, $this->count).$loaded.substr($this->buffer, $this->count);
-					return true;
-				}
-			}
-			// import failed, just write literal
-			$this->setLiteral('@import url("'.$url.'")'.($media ? ' '.$media : '').';');
-			return true;
-		}
-
-		// mixin/function expand
-		if ($this->tags($tags, true, '>') && ($this->argumentValues($argv) || true) && $this->end()) {
-			$block = $this->getEnv($tags);
-			if (is_null($block)) return true;
-
-			$argEnv = array();
-			if (!empty($block['__args'])) {
-				foreach ($block['__args'] as $arg) {
-					$vname = $this->vPrefix.$arg[0];
-					$value = is_array($argv) ? array_shift($argv) : null;
-					// copy default value if there isn't one supplied
-					if ($value == null && isset($arg[1]))
-						$value = $arg[1];
-
-					$argEnv[$vname] = array($value);
-				}
-			}
-
-			unset($block['__args']);
-			$this->push($argEnv);
-			$block = $this->reduceBlock($block);
-			$this->pop();
-
-			$this->merge($block);
-			return true;
-		} else {
-			$this->seek($s);
-		}
-
-		// spare ;
-		if ($this->literal(';')) return true;
-
-		return false; // couldn't match anything, throw error
-	}
-
-	function fileExists($name) {
-		// sym link workaround
-		return file_exists($name) || file_exists(realpath(preg_replace('/\w+\/\.\.\//', '', $name)));
-	}
-
-	// a list of expressions
-	function expressionList(&$exps) {
-		$values = array();	
-
-		while ($this->expression($exp)) {
-			$values[] = $exp;
-		}
-		
-		if (count($values) == 0) return false;
-
-		$exps = $this->compressList($values, ' ');
-		return true;
-	}
-
-	// a single expression
-	function expression(&$out) {
-		$s = $this->seek();
-		$needWhite = true;
-		if ($this->literal('(') && $this->expression($exp) && $this->literal(')')) {
-			$lhs = $exp;
-			$needWhite = false;
-		} elseif ($this->seek($s) && $this->value($val)) {
-			$lhs = $val;
-		} else {
-			return false;
-		}
-
-		$out = $this->expHelper($lhs, 0, $needWhite);
-		return true;
-	}
-
-	// recursively parse infix equation with $lhs at precedence $minP
-	function expHelper($lhs, $minP, $needWhite = true) {
-		$ss = $this->seek();
-		// try to find a valid operator
-		while ($this->match(self::$operatorString.($needWhite ? '\s+' : ''), $m) && self::$precedence[$m[1]] >= $minP) {
-			$needWhite = true;
-			// get rhs
-			$s = $this->seek();
-			if ($this->literal('(') && $this->expression($exp) && $this->literal(')')) {
-				$needWhite = false;
-				$rhs = $exp;
-			} elseif ($this->seek($s) && $this->value($val)) {
-				$rhs = $val;
-			} else break;
-
-			// peek for next operator to see what to do with rhs
-			if ($this->peek(self::$operatorString, $next) && self::$precedence[$next[1]] > $minP) {
-				$rhs = $this->expHelper($rhs, self::$precedence[$next[1]]);
-			}
-
-			// don't evaluate yet if it is dynamic
-			if (in_array($rhs[0], self::$dtypes) || in_array($lhs[0], self::$dtypes))
-				$lhs = array('expression', $m[1], $lhs, $rhs);
-			else
-				$lhs = $this->evaluate($m[1], $lhs, $rhs);
-
-			$ss = $this->seek();
-		}
-		$this->seek($ss);
-
-		return $lhs;
-	}
-
-	// consume a list of values for a property
-	function propertyValue(&$value) {
-		$values = array();	
-		
-		$s = null;
-		while ($this->expressionList($v)) {
-			$values[] = $v;
-			$s = $this->seek();
-			if (!$this->literal(',')) break;
-		}
-
-		if ($s) $this->seek($s);
-
-		if (count($values) == 0) return false;
-
-		$value = $this->compressList($values, ', ');
-		return true;
-	}
-
-	// a single value
-	function value(&$value) {
-		// try a unit
-		if ($this->unit($value)) return true;	
-
-		// see if there is a negation
-		$s = $this->seek();
-		if ($this->literal('-', false) && $this->variable($vname)) {
-			$value = array('negative', array('variable', $this->vPrefix.$vname));
-			return true;
-		} else {
-			$this->seek($s);
-		}
-
-		// accessor 
-		// must be done before color
-		// this needs negation too
-		if ($this->accessor($a)) {
-			$tmp = $this->getEnv($a[0]);
-			if ($tmp && isset($tmp[$a[1]]))
-				$value = end($tmp[$a[1]]);
-			return true;
-		}
-		
-		// color
-		if ($this->color($value)) return true;
-
-		// css function
-		// must be done after color
-		if ($this->func($value)) return true;
-
-		// string
-		if ($this->string($tmp, $d)) {
-			$value = array('string', $d.$tmp.$d);
-			return true;
-		}
-
-		// try a keyword
-		if ($this->keyword($word)) {
-			$value = array('keyword', $word);
-			return true;
-		}
-
-		// try a variable
-		if ($this->variable($vname)) {
-			$value = array('variable', $this->vPrefix.$vname);
-			return true;
-		}
-
-		return false;
-	}
-
-	// an import statement
-	function import(&$url, &$media) {
-		$s = $this->seek();
-		if (!$this->literal('@import')) return false;
-
-		// @import "something.css" media;
-		// @import url("something.css") media;
-		// @import url(something.css) media; 
-
-		if ($this->literal('url(')) $parens = true; else $parens = false;
-
-		if (!$this->string($url)) {
-			if ($parens && $this->to(')', $url)) {
-				$parens = false; // got em
-			} else {
-				$this->seek($s);
-				return false;
-			}
-		}
-
-		if ($parens && !$this->literal(')')) {
-			$this->seek($s);
-			return false;
-		}
-
-		// now the rest is media
-		return $this->to(';', $media, false, true);
-	}
-
-	// a list of media types, very lenient
-	function mediaTypes(&$types, &$rest) {
-		$s = $this->seek();
-		$types = array();
-		while ($this->match('([^,{\s]+)', $m)) {
-			$types[] = $m[1];
-			if (!$this->literal(',')) break;
-		}
-
-		// get everything else
-		if ($this->to('{', $rest, true, true)) {
-			$rest = trim($rest);
-		}
-
-		return count($types) > 0;
-	}
-
-	// a scoped value accessor
-	// .hello > @scope1 > @scope2['value'];
-	function accessor(&$var) {
-		$s = $this->seek();
-
-		if (!$this->tags($scope, true, '>') || !$this->literal('[')) {
-			$this->seek($s);
-			return false;
-		}
-
-		// either it is a variable or a property
-		// why is a property wrapped in quotes, who knows!
-		if ($this->variable($name)) {
-			$name = $this->vPrefix.$name;
-		} elseif ($this->literal("'") && $this->keyword($name) && $this->literal("'")) {
-			// .. $this->count is messed up if we wanted to test another access type
-		} else {
-			$this->seek($s);
-			return false;
-		}
-
-		if (!$this->literal(']')) {
-			$this->seek($s);
-			return false;
-		}
-
-		$var = array($scope, $name);
-		return true;
-	}
-
-	// a string 
-	function string(&$string, &$d = null) {
-		$s = $this->seek();
-		if ($this->literal('"', false)) {
-			$delim = '"';
-		} elseif ($this->literal("'", false)) {
-			$delim = "'";
-		} else {
-			return false;
-		}
-
-		if (!$this->to($delim, $string)) {
-			$this->seek($s);
-			return false;
-		}
-		
-		$d = $delim;
-		return true;
-	}
-
-	// a numerical unit
-	function unit(&$unit, $allowed = null) {
-		$simpleCase = $allowed == null;
-		if (!$allowed) $allowed = self::$units;
-
-		if ($this->match('(-?[0-9]*(\.)?[0-9]+)('.implode('|', $allowed).')?', $m, !$simpleCase)) {
-			if (!isset($m[3])) $m[3] = 'number';
-			$unit = array($m[3], $m[1]);
-
-			// check for size/height font unit.. should this even be here?
-			if ($simpleCase) {
-				$s = $this->seek();
-				if ($this->literal('/', false) && $this->unit($right, self::$units)) {
-					$unit = array('keyword', $this->compileValue($unit).'/'.$this->compileValue($right));
-				} else {
-					// get rid of whitespace
-					$this->seek($s);
-					$this->match('', $_);
-				}
-			}
-
-			return true;
-		}
-
-		return false;
-	}
-
-	// a # color
-	function color(&$out) {
-		$color = array('color');
-
-		if ($this->match('(#([0-9a-f]{6})|#([0-9a-f]{3}))', $m)) {
-			if (isset($m[3])) {
-				$num = $m[3];
-				$width = 16;
-			} else {
-				$num = $m[2];
-				$width = 256;
-			}
-
-			$num = hexdec($num);
-			foreach (array(3,2,1) as $i) {
-				$t = $num % $width;
-				$num /= $width;
-
-				$color[$i] = $t * (256/$width) + $t * floor(16/$width);
-			}
-			
-			$out = $color;
-			return true;
-		} 
-
-		return false;
-	}
-
-	// consume a list of property values delimited by ; and wrapped in ()
-	function argumentValues(&$args, $delim = ';') {
-		$s = $this->seek();
-		if (!$this->literal('(')) return false;
-
-		$values = array();
-		while (true) {
-			if ($this->propertyValue($value)) $values[] = $value;
-			if (!$this->literal($delim)) break;
-			else {
-				if ($value == null) $values[] = null;
-				$value = null;
-			}
-		}	
-
-		if (!$this->literal(')')) {
-			$this->seek($s);
-			return false;
-		}
-		
-		$args = $values;
-		return true;
-	}
-
-	// consume an argument definition list surrounded by ()
-	// each argument is a variable name with optional value
-	function argumentDef(&$args, $delim = ';') {
-		$s = $this->seek();
-		if (!$this->literal('(')) return false;
-
-		$values = array();
-		while ($this->variable($vname)) {
-			$arg = array($vname);
-			if ($this->assign() && $this->propertyValue($value)) {
-				$arg[] = $value;
-				// let the : slide if there is no value
-			}
-
-			$values[] = $arg;
-			if (!$this->literal($delim)) break;
-		}
-
-		if (!$this->literal(')')) {
-			$this->seek($s);
-			return false;
-		}
-
-		$args = $values;
-		return true;
-	}
-
-	// consume a list of tags
-	// this accepts a hanging delimiter
-	function tags(&$tags, $simple = false, $delim = ',') {
-		$tags = array();
-		while ($this->tag($tt, $simple)) {
-			$tags[] = $tt;
-			if (!$this->literal($delim)) break;
-		}
-		if (count($tags) == 0) return false;
-
-		return true;
-	}
-
-	// a bracketed value (contained within in a tag definition)
-	function tagBracket(&$value) {
-		$s = $this->seek();
-		if ($this->literal('[') && $this->to(']', $c, true) && $this->literal(']', false)) {
-			$value = '['.$c.']';
-			// whitespace?
-			if ($this->match('', $_)) $value .= $_[0];
-			return true;
-		}
-
-		$this->seek($s);
-		return false;
-	}
-
-	// a single tag
-	function tag(&$tag, $simple = false) {
-		if ($simple)
-			$chars = '^,:;{}\][>\(\) ';
-		else
-			$chars = '^,;{}[';
-
-		$tag = '';
-		while ($this->tagBracket($first)) $tag .= $first;
-		while ($this->match('(['.$chars.'0-9]['.$chars.']*)', $m)) {
-			$tag .= $m[1];
-			if ($simple) break;
-
-			while ($this->tagBracket($brack)) $tag .= $brack;
-		}
-		$tag = trim($tag);
-		if ($tag == '') return false;
-
-		return true;
-	}
-
-	// a css function
-	function func(&$func) {
-		$s = $this->seek();
-
-		if ($this->match('([\w\-_][\w\-_:\.]*)', $m) && $this->literal('(')) {
-			$fname = $m[1];
-			if ($fname == 'url') {
-				$this->to(')', $content, true);
-				$args = array('string', $content);
-			} else {
-				$args = array();
-				while (true) {
-					$ss = $this->seek();
-					if ($this->keyword($name) && $this->literal('=') && $this->expressionList($value)) {
-						$args[] = array('list', '=', array(array('keyword', $name), $value));
-					} else {
-						$this->seek($ss);
-						if ($this->expressionList($value)) {
-							$args[] = $value;
-						}
-					}
-
-					if (!$this->literal(',')) break;
-				}
-				$args = array('list', ',', $args);
-			}
-
-			if ($this->literal(')')) {
-				$func = array('function', $fname, $args);
-				return true;
-			}
-		}
-
-		$this->seek($s);
-		return false;
-	}
-
-	// consume a less variable
-	function variable(&$name) {
-		$s = $this->seek();
-		if ($this->literal($this->vPrefix, false) && $this->keyword($name)) {
-			return true;	
-		}
-
-		return false;
-	}
-
-	// consume an assignment operator
-	function assign() {
-		return $this->literal(':') || $this->literal('=');
-	}
-
-	// consume a keyword
-	function keyword(&$word) {
-		if ($this->match('([\w_\-\*!"][\w\-_"]*)', $m)) {
-			$word = $m[1];
-			return true;
-		}
-		return false;
-	}
-
-	// consume an end of statement delimiter
-	function end() {
-		if ($this->literal(';'))
-			return true;
-		elseif ($this->count == strlen($this->buffer) || $this->buffer{$this->count} == '}') {
-			// if there is end of file or a closing block next then we don't need a ;
-			return true;
-		}
-		return false;
-	}
-
-	function compressList($items, $delim) {
-		if (count($items) == 1) return $items[0];	
-		else return array('list', $delim, $items);
-	}
-
-	/**
-	 * Recursively compiles a block. 
-	 * @param $block the block
-	 * @param $parentTags the tags of the block that contained this one
-	 * @param $bindEnv true if we should bind the block before compiling
-	 *
-	 * A block is analogous to a CSS block in most cases. A single less document
-	 * is encapsulated in a block when parsed, but it does not have parent tags
-	 * so all of it's children appear on the root level when compiled.
-	 *
-	 * A block is stored in a PHP array(). Each entry in the array represents
-	 * some structure. The key is the name, and the value is the value of the
-	 * structure.  Some structures do not have names, they are prefixed with
-	 * either __literal or __special in order to be differentiated. Some
-	 * additional meta-data which is not to be printed is also stored in a
-	 * block, their names begining with __. (eg. __tags, __args)
-	 *
-	 * Because in less, CSS blocks can be described by nesting, compileBlock
-	 * must be aware of where it came from. The argument $parentTags stores the
-	 * selector tags of where the block was defined, null represents no parents.
-	 *
-	 * The __tags meta-data in the block stores the actual selector tags the
-	 * block has. (We can't just use the key in the array because sometimes a
-	 * block can have multiple selector tags separated by ,) The parent tags
-	 * are "multiplied" against the __tags in order to get all the real
-	 * selectors that describe the block. (This value is also recursively
-	 * passed to compileBlock when compiling sub blocks).
-	 *
-	 * After that, if the block has any arguments with default values they are
-	 * inserted as an environment so their values can be accessed when reducing
-	 * any variables.
-	 *
-	 * The block is then iterated on, compiling each component individually. If
-	 * another block is found, then it is recursively compiled.
-	 *
-	 */
-	function compileBlock($block, $parentTags = null, $bindEnv = true) {
-		$children = array();
-		$visitedMixins = array(); // mixins to skip
-		$props = 0;
-
-		// multiply tags
-		if (isset($block['__tags'])) {
-			$tags = array();
-			foreach ($parentTags as $outerTag) {
-				foreach ($block['__tags'] as $innerTag) {
-					$tags[] = trim($outerTag.
-						($innerTag{0} == $this->selfSelector || $innerTag{0} == ':'
-							? ltrim($innerTag, $this->selfSelector) : ' '.$innerTag));
-				}
-			}
-		} else {
-			$tags = $parentTags;
-		}
-
-		// insert default args -- does not exist for blocks already reduced
-		if (isset($block['__args'])) {
-			$this->push();
-			foreach ($block['__args'] as $arg) {
-				if (isset($arg[1])) $this->append($this->vPrefix.$arg[0], $arg[1]);
-			}
-		}
-
-		ob_start();
-		if ($bindEnv) $this->push($block);
-		foreach ($block as $name => $value) {
-			if ($this->isProperty($name, $value)) {
-				echo $this->compileProperty($name, $value, is_null($tags) ? 0 : 1)."\n";
-				$props += count($value);
-			} elseif ($this->isBlock($name, $value)) {
-				if (isset($visitedMixins[$name])) continue;
-
-				foreach ($value['__tags'] as $tag) {
-					$visitedMixins[$tag] = true;
-				}
-
-				$child = $this->compileBlock($value, is_null($tags) ? array('') : $tags);
-				if (is_null($tags)) echo $child;
-				else $children[] = $child;
-			} else {
-				if ($name == '__special') continue;
-
-				if (is_string($value)) {
-					echo $this->indent($value);
-				} elseif (isset($value['__special'])) {
-					$this->indentLevel++;
-					switch ($value['__special']) {
-					case 'media':
-						list($types, $rest) = $value['__media'];
-						echo "@media ".join(', ', $types).(!empty($rest) ? " $rest" : '' )." {\n";
-						break;
-					case 'font-face':
-						echo "@font-face {\n";
-						break;
-					case 'keyframes':
-						list($prefix, $typeValue) = $value['__keyframes'];
-						echo $prefix.$this->compileValue($typeValue)." {\n";
-						break;
-					}
-					echo $this->compileBlock($value);
-					echo "}\n";
-					$this->indentLevel--;
-				}
-			}
-		}
-		if ($bindEnv) $this->pop();
-
-		if (isset($block['__args'])) $this->pop();
-
-		$list = ob_get_clean();
-
-		if ($tags == null) {
-			$out = $list;
-		} else {
-			$blockDecl = implode(", ", $tags).' {';
-
-			if ($props > 1)
-				$out = $this->indent($blockDecl).$list.$this->indent('}');
-			elseif ($props == 1) {
-				$list = ' '.trim($list).' ';
-				$out = $this->indent($blockDecl.$list.'}');
-			} else $out = '';
-		}
-
-		return $out.implode('', $children);
-	}
-
-	// write a line a the proper indent
-	function indent($str, $level = null) {
-		if (is_null($level)) $level = $this->indentLevel;
-		return str_repeat($this->indentChar, $level).$str."\n";
-	}
-
-	function compileProperty($name, $value, $level = 0) {
-		$level = $this->indentLevel + $level;
-		// output all repeated properties
-		foreach ($value as $v)
-			$props[] = str_repeat($this->indentChar, $level).
-				$name.':'.$this->compileValue($v).';';
-
-		return implode("\n", $props);
-	}
-
-	/**
-	 * Compiles a typed value into a CSS string.
-	 * @param $value the value to compile
-	 *
-	 * Values in lessphp are typed by being wrapped in arrays, their format is
-	 * typically:
-	 *
-	 * 	array(type, contents [, additional_contents]*)
-	 *
-	 * By switching on the type, we can run the respective compile function.
-	 * Some values are recursively compiled. This function is safe to run on
-	 * any value, it will reduce values that don't have a concrete value yet.
-	 */
-	function compileValue($value) {
-		switch ($value[0]) {
-		case 'list':
-			// [1] - delimiter
-			// [2] - array of values
-			return implode($value[1], array_map(array($this, 'compileValue'), $value[2]));
-		case 'keyword':
-			// [1] - the keyword 
-		case 'number':
-			// [1] - the number 
-			return $value[1];
-		case 'expression':
-			// [1] - operator
-			// [2] - value of left hand side
-			// [3] - value of right
-			return $this->compileValue($this->evaluate($value[1], $value[2], $value[3]));
-		case 'string':
-			// [1] - contents of string (includes quotes)
-			
-			// search for inline variables to replace
-			$replace = array();
-			if (preg_match_all('/{('.$this->preg_quote($this->vPrefix).'[\w-_][0-9\w-_]*?)}/', $value[1], $m)) {
-				foreach ($m[1] as $name) {
-					if (!isset($replace[$name]))
-						$replace[$name] = $this->compileValue(array('variable', $name));
-				}
-			}
-			foreach ($replace as $var=>$val) {
-				// strip quotes
-				if (preg_match('/^(["\']).*?(\1)$/', $val)) {
-					$val = substr($val, 1, -1);
-				}
-				$value[1] = str_replace('{'.$var.'}', $val, $value[1]);
-			}
-
-
-			return $value[1];
-		case 'color':
-			// [1] - red component (either number for a %)
-			// [2] - green component
-			// [3] - blue component
-			// [4] - optional alpha component
-			if (count($value) == 5) { // rgba
-				return 'rgba('.$value[1].','.$value[2].','.$value[3].','.$value[4].')';
-			}
-			return sprintf("#%02x%02x%02x", $value[1], $value[2], $value[3]);
-		case 'variable':
-			// [1] - the name of the variable including @
-			$tmp = $this->compileValue(
-				$this->getVal($value[1], $this->pushName($value[1]))
-			);
-			$this->popName();
-
-			return $tmp;
-		case 'negative':
-			// [1] - some value that needs to become negative
-			return $this->compileValue($this->reduce($value));
-		case 'function':
-			// [1] - function name
-			// [2] - some value representing arguments
-
-			// see if function evaluates to something else
-			$value = $this->reduce($value);
-			if ($value[0] == 'function') {
-				return $value[1].'('.$this->compileValue($value[2]).')';
-			}
-			else return $this->compileValue($value);
-		default: // assumed to be unit	
-			return $value[1].$value[0];
-		}
-	}
-
-	function lib_rgbahex($arg) {
-		$color = $this->reduce($arg);
-		if ($color[0] != 'color')
-			throw new exception("color expected for rgbahex");
-
-		return sprintf("#%02x%02x%02x%02x",
-			isset($color[4]) ? $color[4]*255 : 0,
-			$color[1],$color[2], $color[3]);
-	}
-
-	function lib_quote($arg) {
-		return '"'.$this->compileValue($arg).'"';
-	}
-
-	function lib_unquote($arg) {
-		$out = $this->compileValue($arg);
-		if ($this->quoted($out)) $out = substr($out, 1, -1);
-		return $out;
-	}
-
-	function lib_floor($arg) {
-		$arg = $this->reduce($arg);
-		return floor($arg[1]);
-	}
-
-	function lib_round($arg) {
-		$arg = $this->reduce($arg);
-		return round($arg[1]);
-	}
-
-	// is a string surrounded in quotes? returns the quoting char if true
-	function quoted($s) {
-		if (preg_match('/^("|\').*?\1$/', $s, $m))
-			return $m[1];
-		else return false;
-	}
-
-	// convert rgb, rgba into color type suitable for math
-	// todo: add hsl
-	function funcToColor($func) {
-		$fname = $func[1];
-		if (!preg_match('/^(rgb|rgba)$/', $fname)) return false;
-		if ($func[2][0] != 'list') return false; // need a list of arguments
-
-		$components = array();
-		$i = 1;
-		foreach	($func[2][2] as $c) {
-			$c = $this->reduce($c);
-			if ($i < 4) {
-				if ($c[0] == '%') $components[] = 255 * ($c[1] / 100);
-				else $components[] = floatval($c[1]); 
-			} elseif ($i == 4) {
-				if ($c[0] == '%') $components[] = 1.0 * ($c[1] / 100);
-				else $components[] = floatval($c[1]);
-			} else break;
-
-			$i++;
-		}
-		while (count($components) < 3) $components[] = 0;
-
-		array_unshift($components, 'color');
-		return $this->fixColor($components);
-	}
-
-	// reduce an entire block, removing any delayed types
-	// done before a mixin is mixed in
-	function reduceBlock($block) {
-		if (isset($block['__args'])) {
-			$this->push();
-			foreach ($block['__args'] as $arg) {
-				if (isset($arg[1])) $this->append($this->vPrefix.$arg[0], $arg[1]);
-			}
-		}
-
-		$this->push();
-		foreach ($block as $name => $value) {
-			if ($this->isProperty($name, $value, false)) {
-				foreach ($value as $v) {
-					$value = $this->reduce($v);
-					$this->append($name, $value);
-				}
-			} elseif ($this->isBlock($name, $value, false)) {
-				$this->set($name, $this->reduceBlock($value));
-			} else {
-				if ($name != '__args') $this->set($name, $value);
-			}
-		}
-
-		$out = $this->pop();
-		if (isset($block['__args'])) {
-			$this->pop();
-		}
-		return $out;
-	}
-
-	// reduce a delayed type to its final value
-	// dereference variables and solve equations
-	function reduce($var, $defaultValue = array('number', 0)) {
-		$pushed = 0; // number of variable names pushed
-
-		while (in_array($var[0], self::$dtypes)) {
-			if ($var[0] == 'list') {
-				foreach ($var[2] as &$value) $value = $this->reduce($value);
-				break;
-			} elseif ($var[0] == 'expression') {
-				$var = $this->evaluate($var[1], $var[2], $var[3]);
-			} elseif ($var[0] == 'variable') {
-				$var = $this->getVal($var[1], $this->pushName($var[1]), $defaultValue);
-				$pushed++;
-			} elseif ($var[0] == 'function') {
-				$color = $this->funcToColor($var);
-				if ($color) $var = $color;
-				else {
-					$f = array($this, 'lib_'.$var[1]);
-					if (is_callable($f)) {
-						list($_, $delim, $items) = $var[2];
-						$var = call_user_func($f, $this->compressList($items, $delim));
-						if (is_numeric($var)) $var = array('number', $var);
-						elseif (!is_array($var)) $var = array('keyword', $var);
-					} else {
-						// plain function, reduce args
-						$var[2] = $this->reduce($var[2]);
-					}
-				}
-				break; // no where to go after a function
-			} elseif ($var[0] == 'negative') {
-				$value = $this->reduce($var[1]);
-				if (is_numeric($value[1])) {
-					$value[1] = -1*$value[1];
-				} 
-				$var = $value;
-			}
-		}
-
-		while ($pushed != 0) { $this->popName(); $pushed--; }
-		return $var;
-	}
-
-	// evaluate an expression
-	function evaluate($op, $left, $right) {
-		$left = $this->reduce($left);
-		$right = $this->reduce($right);
-
-		if ($left[0] == 'color' && $right[0] == 'color') {
-			$out = $this->op_color_color($op, $left, $right);
-			return $out;
-		}
-
-		if ($left[0] == 'color') {
-			return $this->op_color_number($op, $left, $right);
-		}
-
-		if ($right[0] == 'color') {
-			return $this->op_number_color($op, $left, $right);
-		}
-
-		// concatenate strings
-		if ($op == '+' && $left[0] == 'string') {
-			$append = $this->compileValue($right);
-			if ($this->quoted($append)) $append = substr($append, 1, -1);
-
-			$lhs = $this->compileValue($left);
-			if ($q = $this->quoted($lhs)) $lhs = substr($lhs, 1, -1);
-			if (!$q) $q = '';
-
-			return array('string', $q.$lhs.$append.$q);
-		}
-
-		if ($left[0] == 'keyword' || $right[0] == 'keyword' ||
-			$left[0] == 'string' || $right[0] == 'string')
-		{
-			// look for negative op
-			if ($op == '-') $right[1] = '-'.$right[1];
-			return array('keyword', $this->compileValue($left) .' '. $this->compileValue($right));
-		}
-	
-		// default to number operation
-		return $this->op_number_number($op, $left, $right);
-	}
-
-	// make sure a color's components don't go out of bounds
-	function fixColor($c) {
-		foreach (range(1, 3) as $i) {
-			if ($c[$i] < 0) $c[$i] = 0;
-			if ($c[$i] > 255) $c[$i] = 255;
-			$c[$i] = floor($c[$i]);
-		}
-
-		return $c;
-	}
-
-	function op_number_color($op, $lft, $rgt) {
-		if ($op == '+' || $op = '*') {
-			return $this->op_color_number($op, $rgt, $lft);
-		}
-	}
-
-	function op_color_number($op, $lft, $rgt) {
-		if ($rgt[0] == '%') $rgt[1] /= 100;
-
-		return $this->op_color_color($op, $lft,
-			array_fill(1, count($lft) - 1, $rgt[1]));
-	}
-
-	function op_color_color($op, $left, $right) {
-		$out = array('color');
-		$max = count($left) > count($right) ? count($left) : count($right);
-		foreach (range(1, $max - 1) as $i) {
-			$lval = isset($left[$i]) ? $left[$i] : 0;
-			$rval = isset($right[$i]) ? $right[$i] : 0;
-			switch ($op) {
-			case '+':
-				$out[] = $lval + $rval;
-				break;
-			case '-':
-				$out[] = $lval - $rval;
-				break;
-			case '*':
-				$out[] = $lval * $rval;
-				break;
-			case '%':
-				$out[] = $lval % $rval;
-				break;
-			case '/':
-				if ($rval == 0) throw new exception("evaluate error: can't divide by zero");
-				$out[] = $lval / $rval;
-				break;
-			default:
-				throw new exception('evaluate error: color op number failed on op '.$op);
-			}
-		}
-		return $this->fixColor($out);
-	}
-
-	// operator on two numbers
-	function op_number_number($op, $left, $right) {
-		if ($right[0] == '%') $right[1] /= 100;
-
-		// figure out type
-		if ($right[0] == 'number' || $right[0] == '%') $type = $left[0];
-		else $type = $right[0];
-
-		$value = 0;
-		switch ($op) {
-		case '+':
-			$value = $left[1] + $right[1];
-			break;	
-		case '*':
-			$value = $left[1] * $right[1];
-			break;	
-		case '-':
-			$value = $left[1] - $right[1];
-			break;	
-		case '%':
-			$value = $left[1] % $right[1];
-			break;	
-		case '/':
-			if ($right[1] == 0) throw new exception('parse error: divide by zero');
-			$value = $left[1] / $right[1];
-			break;
-		default:
-			throw new exception('parse error: unknown number operator: '.$op);	
-		}
-
-		return array($type, $value);
-	}
-
-
-	/* environment functions */
-
-	// push name on expand stack, and return its 
-	// count before being pushed
-	function pushName($name) {
-		$count = array_count_values($this->expandStack);
-		$count = isset($count[$name]) ? $count[$name] : 0;
-
-		$this->expandStack[] = $name;
-
-		return $count;
-	}
-
-	// pop name off expand stack and return it
-	function popName() {
-		return array_pop($this->expandStack);
-	}
-
-	// push a new environment
-	// $base is initial store
-	function push($base = null) {
-		$env = new stdclass;
-		$env->parent = $this->env;
-		$env->store = is_null($base) ? array() : $base;
-
-		$this->env = $env;
-	}
-
-	// pop environment off the stack
-	function pop() {
-		if (is_null($this->env->parent))
-			throw new exception('parse error: unexpected end of block');
-
-		$old = $this->env;
-		$this->env = $this->env->parent;
-		return $old->store;
-	}
-
-	// set something in the current env
-	function set($name, $value) {
-		$this->env->store[$name] = $value;
-	}
-
-	// set some literal value at the end of the parse
-	function setLiteral($value) {
-		$this->env->store['__literal'.count($this->env->store)] = $value;
-	}
-
-	// append to array in the current env
-	function append($name, $value) {
-		$this->env->store[$name][] = $value;
-	}
-
-	// append a list of values to name
-	function appendAll($name, $values) {
-		foreach ($values as $value)
-			$this->env->store[$name][] = $value;
-	}
-
-	// put on the front of the value
-	function prepend($name, $value) {
-		if (isset($this->env->store[$name]))
-			array_unshift($this->env->store[$name], $value);
-		else $this->append($name, $value);
-	}
-
-	function getRaw($name, $failValue = false) {
-		if (isset($this->env->store[$name])) {
-			return $this->env->store[$name];
-		}
-		return $failValue;
-	}
-
-	// get the highest occurrence entry for a name
-	// optionally start at environment $top
-	function get($name, $top = null) {
-		$current = is_null($top) ? $this->env : $top;
-
-		while ($current) {
-			if (isset($current->store[$name]))
-				return $current->store[$name];
-			else
-				$current = $current->parent;
-		}
-
-		return null;
-	}
-
-	// get the highest occurrence value for a name,
-	// while skipping $skip values from the top.
-	// return default if it isn't found
-	function getVal($name, $skip = 0, $default = array('keyword', '')) {
-		$values = $this->get($name);
-		if (is_null($values)) return $default;
-
-		$current = $this->env;
-		while ($skip > 0) {
-			$skip--;
-
-			if (!empty($values)) {
-				array_pop($values);
-			}
-
-			if (empty($values) && !is_null($current->parent)) {
-				$current = $current->parent;
-				$values = $this->get($name, $current);
-			}
-
-			if (empty($values)) return $default;
-		}
-
-		return end($values);
-	}
-
-	// follow path of names from current env to get entry value
-	// should be called getBlock or something (this is not getting an environment!)
-	function getEnv($path) {
-		if (!is_array($path)) $path = array($path);
-
-		//  move @ tags out of variable namespace
-		foreach ($path as &$_tag) {
-			if ($_tag{0} == $this->vPrefix) $_tag[0] = $this->mPrefix;
-		}
-
-		$block = $this->get(array_shift($path));
-		foreach ($path as $tag) {
-			if (is_array($block) && isset($block[$tag])) {
-				$block = $block[$tag];
-			} else return null;
-		}
-
-		return $block;
-	}
-
-	/**
-	 * Merge a block into the current environment.
-	 * @param $block the block to merge
-	 *
-	 * In order to reduce redundant blocks from being created, existing blocks
-	 * with the same selectors are searched. If they exist, merge will attempt
-	 * to recursively merge all their children.
-	 *
-	 * This is done at the expense of duplicating properties in the output.
-	 *
-	 * The selector tags from the block to be merged, and the conflicting block
-	 * are broken into three sets: $shared, the tags that are common between the
-	 * two; $broken, the unique tags the existing block has; $split, the 
-	 * unique tags that the block being merged has. The __tags meta-data can be
-	 * set to their new values respectively and the merge can take place as
-	 * intended.
-	 */
-	function merge($block) {
-		// see if we have to rework __tags, mixing into some of bound blocks breaks them up
-		foreach ($block as $name => $value) {
-			if (!$this->isBlock($name, $value, false)) continue;
-
-			$other = $this->getRaw($name);
-			if ($other && $other['__tags'] != $value['__tags']) {
-
-				$source = $this->env->store[$name]['__tags'];
-				$dest = $value['__tags'];
-
-				$shared = array_values(array_intersect($source, $dest));
-
-				$broken = array_values(array_diff($source, $dest));
-				$split = array_values(array_diff($dest, $source));
-
-				$this->env->store[$name]['__tags'] = $shared;
-				foreach ($broken as $brokenName) {
-					$this->env->store[$brokenName]['__tags'] = $broken;
-				}
-
-				foreach ($split as $splitName) {
-					$block[$splitName]['__tags'] = $split;
-				}
-			}
-		}
-
-		foreach ($block as $name => $value) {
-			if ($this->isProperty($name, $value, false)) {
-				$this->appendAll($name, $value);
-			} elseif ($this->isBlock($name, $value, false)) {
-				if ($subBlock = $this->getRaw($name)) {
-					// echo "merging $name\n";
-					$this->push($subBlock);
-					$this->merge($value);
-					$this->set($name, $this->pop());
-				} else {
-					$this->set($name, $value);
-				}
-			}
-		}
-	}
-	
-	function isProperty($name, $value, $isConcrete = true) {
-		return is_array($value) && array_key_exists(0, $value) &&
-			substr($name, 0,2) != '__' &&
-			(!$isConcrete || $name{0} != $this->vPrefix);
-	}
-
-	function isBlock($name, $value, $isConcrete = true) {
-		return is_array($value) && !array_key_exists(0, $value) &&
-			substr($name, 0, 2) != '__' &&
-			(!$isConcrete || $name{0} != $this->mPrefix);
-	}
-
-	function literal($what, $eatWhitespace = true) {
-		// this is here mainly prevent notice from { } string accessor 
-		if ($this->count >= strlen($this->buffer)) return false;
-
-		// shortcut on single letter
-		if (!$eatWhitespace and strlen($what) == 1) {
-			if ($this->buffer{$this->count} == $what) {
-				$this->count++;
-				return true;
-			}
-			else return false;
-		}
-
-		return $this->match($this->preg_quote($what), $m, $eatWhitespace);
-	}
-
-	function preg_quote($what) {
-		return preg_quote($what, '/');
-	}
-
-	// advance counter to next occurrence of $what
-	// $until - don't include $what in advance
-	function to($what, &$out, $until = false, $allowNewline = false) {
-		$validChars = $allowNewline ? "[^\n]" : '.';
-		if (!$this->match('('.$validChars.'*?)'.$this->preg_quote($what), $m, !$until)) return false;
-		if ($until) $this->count -= strlen($what); // give back $what
-		$out = $m[1];
-		return true;
-	}
-	
-	// try to match something on head of buffer
-	function match($regex, &$out, $eatWhitespace = true) {
-		$r = '/'.$regex.($eatWhitespace ? '\s*' : '').'/Ais';
-		if (preg_match($r, $this->buffer, $out, null, $this->count)) {
-			$this->count += strlen($out[0]);
-			return true;
-		}
-		return false;
-	}
-
-	// match something without consuming it
-	function peek($regex, &$out = null) {
-		$r = '/'.$regex.'/Ais';
-		$result =  preg_match($r, $this->buffer, $out, null, $this->count);
-		
-		return $result;
-	}
-
-	// seek to a spot in the buffer or return where we are on no argument
-	function seek($where = null) {
-		if ($where === null) return $this->count;
-		else $this->count = $where;
-		return true;
-	}
-
-	/**
-	 * Initialize state for a fresh parse
-	 */
-	protected function prepareParser($buff) {
-		$this->env = null;
-		$this->expandStack = array();
-		$this->indentLevel = 0;
-		$this->count = 0;
-		$this->line = 1;
-
-		$this->buffer = $this->removeComments($buff);
-		$this->push(); // set up global scope
-
-		// trim whitespace on head
-		if (preg_match('/^\s+/', $this->buffer, $m)) {
-			$this->line  += substr_count($m[0], "\n");
-			$this->buffer = ltrim($this->buffer);
-		}
-	}
-	
-	// parse and compile buffer
-	function parse($str = null) {
-		$this->prepareParser($str ? $str : $this->buffer);
-		while (false !== $this->parseChunk());
-
-		if ($this->count != strlen($this->buffer)) $this->throwParseError();
-
-		if (!is_null($this->env->parent))
-			throw new exception('parse error: unclosed block');
-
-		return $this->compileBlock($this->env->store, null, false);
-	}
-
-	function throwParseError($msg = 'parse error') {
-		$line = $this->line + substr_count(substr($this->buffer, 0, $this->count), "\n");
-		if ($this->peek("(.*?)(\n|$)", $m))
-			throw new exception($msg.': failed at `'.$m[1].'` line: '.$line);
-	}
-
-	/**
-	 * Initialize any static state, can initialize parser for a file
-	 */
-	function __construct($fname = null, $opts = null) {
-		if (!self::$operatorString) {
-			self::$operatorString = 
-				'('.implode('|', array_map(array($this, 'preg_quote'), array_keys(self::$precedence))).')';
-		}
-
-		if ($fname) {
-			if (!is_file($fname)) {
-				throw new Exception('load error: failed to find '.$fname);
-			}
-			$pi = pathinfo($fname);
-
-			$this->fileName = $fname;
-			$this->importDir = $pi['dirname'].'/';
-			$this->buffer = file_get_contents($fname);
-
-			$this->addParsedFile($fname);
-		}
-	}
-
-	// remove comments from $text
-	// todo: make it work for all functions, not just url
-	function removeComments($text) {
-		$look = array(
-			'url(', '//', '/*', '"', "'"
-		);
-
-		$out = '';
-		$min = null;
-		$done = false;
-		while (true) {
-			// find the next item
-			foreach ($look as $token) {
-				$pos = strpos($text, $token);
-				if ($pos !== false) {
-					if (!isset($min) || $pos < $min[1]) $min = array($token, $pos);
-				}
-			}
-
-			if (is_null($min)) break;
-
-			$count = $min[1];
-			$skip = 0;
-			$newlines = 0;
-			switch ($min[0]) {
-			case 'url(':
-				if (preg_match('/url\(.*?\)/', $text, $m, 0, $count))
-					$count += strlen($m[0]) - strlen($min[0]);
-				break;
-			case '"':
-			case "'":
-				if (preg_match('/'.$min[0].'.*?'.$min[0].'/', $text, $m, 0, $count))
-					$count += strlen($m[0]) - 1;
-				break;
-			case '//':
-				$skip = strpos($text, "\n", $count) - $count;
-				break;
-			case '/*': 
-				if (preg_match('/\/\*.*?\*\//s', $text, $m, 0, $count)) {
-					$skip = strlen($m[0]);
-					$newlines = substr_count($m[0], "\n");
-				}
-				break;
-			}
-
-			if ($skip == 0) $count += strlen($min[0]);
-
-			$out .= substr($text, 0, $count).str_repeat("\n", $newlines);
-			$text = substr($text, $count + $skip);
-
-			$min = null;
-		}
-
-		return $out.$text;
-	}
-
-	public function allParsedFiles() { return $this->allParsedFiles; }
-	protected function addParsedFile($file) {
-		$this->allParsedFiles[realpath($file)] = filemtime($file);
-	}
-
-
-	// compile to $in to $out if $in is newer than $out
-	// returns true when it compiles, false otherwise
-	public static function ccompile($in, $out) {
-		if (!is_file($out) || filemtime($in) > filemtime($out)) {
-			$less = new lessc($in);
-			file_put_contents($out, $less->parse());
-			return true;
-		}
-
-		return false;
-	}
-
-	/**
-	 * Execute lessphp on a .less file or a lessphp cache structure
-	 * 
-	 * The lessphp cache structure contains information about a specific
-	 * less file having been parsed. It can be used as a hint for future
-	 * calls to determine whether or not a rebuild is required.
-	 * 
-	 * The cache structure contains two important keys that may be used
-	 * externally:
-	 * 
-	 * compiled: The final compiled CSS
-	 * updated: The time (in seconds) the CSS was last compiled
-	 * 
-	 * The cache structure is a plain-ol' PHP associative array and can
-	 * be serialized and unserialized without a hitch.
-	 * 
-	 * @param mixed $in Input
-	 * @param bool $force Force rebuild?
-	 * @return array lessphp cache structure
-	 */
-	public static function cexecute($in, $force = false) {
-
-		// assume no root
-		$root = null;
-
-		if (is_string($in)) {
-			$root = $in;
-		} elseif (is_array($in) and isset($in['root'])) {
-			if ($force or ! isset($in['files'])) {
-				// If we are forcing a recompile or if for some reason the
-				// structure does not contain any file information we should
-				// specify the root to trigger a rebuild.
-				$root = $in['root'];
-			} elseif (isset($in['files']) and is_array($in['files'])) {
-				foreach ($in['files'] as $fname => $ftime ) {
-					if (!file_exists($fname) or filemtime($fname) > $ftime) {
-						// One of the files we knew about previously has changed
-						// so we should look at our incoming root again.
-						$root = $in['root'];
-						break;
-					}
-				}
-			}
-		} else {
-			// TODO: Throw an exception? We got neither a string nor something
-			// that looks like a compatible lessphp cache structure.
-			return null;
-		}
-
-		if ($root !== null) {
-			// If we have a root value which means we should rebuild.
-			$less = new lessc($root);
-			$out = array();
-			$out['root'] = $root;
-			$out['compiled'] = $less->parse();
-			$out['files'] = $less->allParsedFiles();
-			$out['updated'] = time();
-			return $out;
-		} else {
-			// No changes, pass back the structure
-			// we were given initially.
-			return $in;
-		}
-
-	}
+    protected $buffer;
+    protected $count;
+    protected $line;
+    protected $libFunctions = array();
+    static protected $nextBlockId = 0;
+
+    public $indentLevel;
+    public $indentChar = '  ';
+
+    protected $env = null;
+
+    protected $allParsedFiles = array();
+
+    public $vPrefix = '@'; // prefix of abstract properties
+    public $mPrefix = '$'; // prefix of abstract blocks
+    public $imPrefix = '!'; // special character to add !important
+    public $parentSelector = '&';
+
+    static protected $precedence = array(
+        '+' => 0,
+        '-' => 0,
+        '*' => 1,
+        '/' => 1,
+        '%' => 1,
+    );
+    static protected $operatorString; // regex string to match any of the operators
+
+    // types that have delayed computation
+    static protected $dtypes = array('expression', 'variable',
+        'function', 'negative', 'list', 'lookup');
+
+    /**
+     * @link http://www.w3.org/TR/css3-values/
+     */
+    static protected $units = array(
+        'em', 'ex', 'px', 'gd', 'rem', 'vw', 'vh', 'vm', 'ch', // Relative length units
+        'in', 'cm', 'mm', 'pt', 'pc', // Absolute length units
+        '%', // Percentages
+        'deg', 'grad', 'rad', 'turn', // Angles
+        'ms', 's', // Times
+        'Hz', 'kHz', //Frequencies
+    );
+
+    public $importDisabled = false;
+    public $importDir = '';
+
+    public $compat = false; // lessjs compatibility mode, does nothing right now
+
+    /**
+     * if we are in an expression then we don't need to worry about parsing font shorthand
+     * $inExp becomes true after the first value in an expression, or if we enter parens
+     */
+    protected $inExp = false;
+
+    /**
+     * if we are in parens we can be more liberal with whitespace around operators because
+     * it must evaluate to a single value and thus is less ambiguous.
+     *
+     * Consider:
+     *     property1: 10 -5; // is two numbers, 10 and -5
+     *     property2: (10 -5); // should evaluate to 5
+     */
+    protected $inParens = false;
+
+    /**
+     * Parse a single chunk off the head of the buffer and place it.
+     * @return false when the buffer is empty, or there is an error
+     *
+     * This functions is called repeatedly until the entire document is
+     * parsed.
+     *
+     * This parser is most similar to a recursive descent parser. Single
+     * functions represent discrete grammatical rules for the language, and
+     * they are able to capture the text that represents those rules.
+     *
+     * Consider the function lessc::keyword(). (all parse functions are
+     * structured the same)
+     *
+     * The function takes a single reference argument. When calling the the
+     * function it will attempt to match a keyword on the head of the buffer.
+     * If it is successful, it will place the keyword in the referenced
+     * argument, advance the position in the buffer, and return true. If it
+     * fails then it won't advance the buffer and it will return false.
+     *
+     * All of these parse functions are powered by lessc::match(), which behaves
+     * the same way, but takes a literal regular expression. Sometimes it is
+     * more convenient to use match instead of creating a new function.
+     *
+     * Because of the format of the functions, to parse an entire string of
+     * grammatical rules, you can chain them together using &&.
+     *
+     * But, if some of the rules in the chain succeed before one fails, then
+     * then buffer position will be left at an invalid state. In order to
+     * avoid this, lessc::seek() is used to remember and set buffer positions.
+     *
+     * Before doing a chain, use $s = $this->seek() to remember the current
+     * position into $s. Then if a chain fails, use $this->seek($s) to
+     * go back where we started.
+     */
+    function parseChunk() {
+        if (empty($this->buffer)) return false;
+        $s = $this->seek();
+
+        // setting a property
+        if ($this->keyword($key) && $this->assign() &&
+            $this->propertyValue($value) && $this->end())
+        {
+            $this->append(array('assign', $key, $value));
+            return true;
+        } else {
+            $this->seek($s);
+        }
+
+        // look for special css blocks
+        if ($this->env->parent == null && $this->literal('@', false)) {
+            $this->count--;
+
+            // a font-face block
+            if ($this->literal('@font-face') && $this->literal('{')) {
+                $b = $this->pushSpecialBlock('@font-face');
+                return true;
+            } else {
+                $this->seek($s);
+            }
+
+            // charset
+            if ($this->literal('@charset') && $this->propertyValue($value) &&
+                $this->end())
+            {
+                $this->append(array('charset', $value));
+                return true;
+            } else {
+                $this->seek($s);
+            }
+
+
+            // media
+            if ($this->literal('@media') && $this->mediaTypes($types) &&
+                $this->literal('{'))
+            {
+                $b = $this->pushSpecialBlock('@media');
+                $b->media = $types;
+                return true;
+            } else {
+                $this->seek($s);
+            }
+
+            // css animations
+            if ($this->match('(@(-[a-z]+-)?keyframes)', $m) &&
+                $this->propertyValue($value) && $this->literal('{'))
+            {
+                $b = $this->pushSpecialBlock(trim($m[0]));
+                $b->keyframes = $value;
+                return true;
+            } else {
+                $this->seek($s);
+            }
+        }
+
+        if (isset($this->env->keyframes)) {
+            if ($this->match("(to|from|[0-9]+%)", $m) && $this->literal('{')) {
+                $this->pushSpecialBlock($m[1]);
+                return true;
+            } else {
+                $this->seek($s);
+            }
+        }
+
+        // setting a variable
+        if ($this->variable($name) && $this->assign() &&
+            $this->propertyValue($value) && $this->end())
+        {
+            $this->append(array('assign', $this->vPrefix.$name, $value));
+            return true;
+        } else {
+            $this->seek($s);
+        }
+
+        if ($this->import($url, $media)) {
+            // don't check .css files
+            if (empty($media) && substr_compare($url, '.css', -4, 4) !== 0) {
+                if ($this->importDisabled) {
+                    $this->append(array('raw', '/* import disabled */'));
+                } else {
+                    $path = $this->findImport($url);
+                    if (!is_null($path)) {
+                        $this->append(array('import', $path));
+                        return true;
+                    }
+                }
+            }
+
+            $this->append(array('raw', '@import url("'.$url.'")'.
+                ($media ? ' '.$media : '').';'));
+            return true;
+        }
+
+        // opening parametric mixin
+        if ($this->tag($tag, true) && $this->argumentDef($args) &&
+            $this->literal('{'))
+        {
+            $block = $this->pushBlock($this->fixTags(array($tag)));
+            $block->args = $args;
+            return true;
+        } else {
+            $this->seek($s);
+        }
+
+        // opening a simple block
+        if ($this->tags($tags) && $this->literal('{')) {
+            $tags = $this->fixTags($tags);
+            $this->pushBlock($tags);
+            return true;
+        } else {
+            $this->seek($s);
+        }
+
+        // closing a block
+        if ($this->literal('}')) {
+            try {
+                $block = $this->pop();
+            } catch (exception $e) {
+                $this->seek($s);
+                $this->throwParseError($e->getMessage());
+            }
+
+            $hidden = true;
+            if (!isset($block->args)) foreach ($block->tags as $tag) {
+                if ($tag{0} != $this->mPrefix) {
+                    $hidden = false;
+                    break;
+                }
+            }
+
+            if (!$hidden) $this->append(array('block', $block));
+            foreach ($block->tags as $tag) {
+                if (isset($this->env->children[$tag])) {
+                    $block = $this->mergeBlock($this->env->children[$tag], $block);
+                }
+                $this->env->children[$tag] = $block;
+            }
+
+            return true;
+        }
+
+        // mixin
+        if ($this->mixinTags($tags) &&
+            ($this->argumentValues($argv) || true) && $this->end())
+        {
+            $tags = $this->fixTags($tags);
+            $this->append(array('mixin', $tags, $argv));
+            return true;
+        } else {
+            $this->seek($s);
+        }
+
+        // spare ;
+        if ($this->literal(';')) return true;
+
+        return false; // got nothing, throw error
+    }
+
+    function fixTags($tags) {
+        // move @ tags out of variable namespace
+        foreach ($tags as &$tag) {
+            if ($tag{0} == $this->vPrefix) $tag[0] = $this->mPrefix;
+        }
+        return $tags;
+    }
+
+    // attempts to find the path of an import url, returns null for css files
+    function findImport($url) {
+        foreach ((array)$this->importDir as $dir) {
+            $full = $dir.(substr($dir, -1) != '/' ? '/' : '').$url;
+            if ($this->fileExists($file = $full.'.less') || $this->fileExists($file = $full)) {
+                return $file;
+            }
+        }
+
+        return null;
+    }
+
+    function fileExists($name) {
+        // sym link workaround
+        return file_exists($name) || file_exists(realpath(preg_replace('/\w+\/\.\.\//', '', $name)));
+    }
+
+    // a list of expressions
+    function expressionList(&$exps) {
+        $values = array();
+
+        while ($this->expression($exp)) {
+            $values[] = $exp;
+        }
+
+        if (count($values) == 0) return false;
+
+        $exps = $this->compressList($values, ' ');
+        return true;
+    }
+
+    /**
+     * Attempt to consume an expression.
+     * @link http://en.wikipedia.org/wiki/Operator-precedence_parser#Pseudo-code
+     */
+    function expression(&$out) {
+        $s = $this->seek();
+        if ($this->literal('(') && ($this->inExp = $this->inParens = true) && $this->expression($exp) && $this->literal(')')) {
+            $lhs = $exp;
+        } elseif ($this->seek($s) && $this->value($val)) {
+            $lhs = $val;
+        } else {
+            $this->inParens = $this->inExp = false;
+            $this->seek($s);
+            return false;
+        }
+
+        $out = $this->expHelper($lhs, 0);
+        $this->inParens = $this->inExp = false;
+        return true;
+    }
+
+    /**
+     * recursively parse infix equation with $lhs at precedence $minP
+     */
+    function expHelper($lhs, $minP) {
+        $this->inExp = true;
+        $ss = $this->seek();
+
+        // if the if there was whitespace before the operator, then we require whitespace after
+        // the operator for it to be a mathematical operator.
+
+        $needWhite = false;
+        if (!$this->inParens && preg_match('/\s/', $this->buffer{$this->count - 1})) {
+            $needWhite = true;
+        }
+
+        // try to find a valid operator
+        while ($this->match(self::$operatorString.($needWhite ? '\s' : ''), $m) && self::$precedence[$m[1]] >= $minP) {
+            // get rhs
+            $s = $this->seek();
+            $p = $this->inParens;
+            if ($this->literal('(') && ($this->inParens = true) && $this->expression($exp) && $this->literal(')')) {
+                $this->inParens = $p;
+                $rhs = $exp;
+            } else {
+                $this->inParens = $p;
+                if ($this->seek($s) && $this->value($val)) {
+                    $rhs = $val;
+                } else {
+                    break;
+                }
+            }
+
+            // peek for next operator to see what to do with rhs
+            if ($this->peek(self::$operatorString, $next) && self::$precedence[$next[1]] > $minP) {
+                $rhs = $this->expHelper($rhs, self::$precedence[$next[1]]);
+            }
+
+            // don't evaluate yet if it is dynamic
+            if (in_array($rhs[0], self::$dtypes) || in_array($lhs[0], self::$dtypes))
+                $lhs = array('expression', $m[1], $lhs, $rhs);
+            else
+                $lhs = $this->evaluate($m[1], $lhs, $rhs);
+
+            $ss = $this->seek();
+
+            $needWhite = false;
+            if (!$this->inParens && preg_match('/\s/', $this->buffer{$this->count - 1})) {
+                $needWhite = true;
+            }
+        }
+        $this->seek($ss);
+
+        return $lhs;
+    }
+
+    // consume a list of values for a property
+    function propertyValue(&$value) {
+        $values = array();
+
+        $s = null;
+        while ($this->expressionList($v)) {
+            $values[] = $v;
+            $s = $this->seek();
+            if (!$this->literal(',')) break;
+        }
+
+        if ($s) $this->seek($s);
+
+        if (count($values) == 0) return false;
+
+        $value = $this->compressList($values, ', ');
+        return true;
+    }
+
+    // a single value
+    function value(&$value) {
+        // try a unit
+        if ($this->unit($value)) return true;
+
+        // see if there is a negation
+        $s = $this->seek();
+        if ($this->literal('-', false) && $this->variable($vname)) {
+            $value = array('negative', array('variable', $this->vPrefix.$vname));
+            return true;
+        } else {
+            $this->seek($s);
+        }
+
+        // accessor
+        // must be done before color
+        // this needs negation too
+        if ($this->accessor($a)) {
+            $a[1] = $this->fixTags($a[1]);
+            $value = $a;
+            return true;
+        }
+
+        // color
+        if ($this->color($value)) return true;
+
+        // css function
+        // must be done after color
+        if ($this->func($value)) return true;
+
+        // string
+        if ($this->string($tmp, $d)) {
+            $value = array('string', $d.$tmp.$d);
+            return true;
+        }
+
+        // try a keyword
+        if ($this->keyword($word)) {
+            $value = array('keyword', $word);
+            return true;
+        }
+
+        // try a variable
+        if ($this->variable($vname)) {
+            $value = array('variable', $this->vPrefix.$vname);
+            return true;
+        }
+
+        // unquote string
+        if ($this->literal("~") && $this->string($value, $d)) {
+            $value = array("keyword", $value);
+            return true;
+        } else {
+            $this->seek($s);
+        }
+
+        // css hack: \0
+        if ($this->literal('\\') && $this->match('([0-9]+)', $m)) {
+            $value = array('keyword', '\\'.$m[1]);
+            return true;
+        } else {
+            $this->seek($s);
+        }
+
+        return false;
+    }
+
+    // an import statement
+    function import(&$url, &$media) {
+        $s = $this->seek();
+        if (!$this->literal('@import')) return false;
+
+        // @import "something.css" media;
+        // @import url("something.css") media;
+        // @import url(something.css) media;
+
+        if ($this->literal('url(')) $parens = true; else $parens = false;
+
+        if (!$this->string($url)) {
+            if ($parens && $this->to(')', $url)) {
+                $parens = false; // got em
+            } else {
+                $this->seek($s);
+                return false;
+            }
+        }
+
+        if ($parens && !$this->literal(')')) {
+            $this->seek($s);
+            return false;
+        }
+
+        // now the rest is media
+        return $this->to(';', $media, false, true);
+    }
+
+    // a list of media types, very lenient
+    function mediaTypes(&$types) {
+        if ($this->to('{', $rest, true, true)) {
+            $types = trim($rest);
+            return true;
+        }
+
+        return false;
+    }
+
+    // a scoped value accessor
+    // .hello > @scope1 > @scope2['value'];
+    function accessor(&$var) {
+        $s = $this->seek();
+
+        if (!$this->tags($scope, true, '>') || !$this->literal('[')) {
+            $this->seek($s);
+            return false;
+        }
+
+        // either it is a variable or a property
+        // why is a property wrapped in quotes, who knows!
+        if ($this->variable($name)) {
+            $name = $this->vPrefix.$name;
+        } elseif ($this->literal("'") && $this->keyword($name) && $this->literal("'")) {
+            // .. $this->count is messed up if we wanted to test another access type
+        } else {
+            $this->seek($s);
+            return false;
+        }
+
+        if (!$this->literal(']')) {
+            $this->seek($s);
+            return false;
+        }
+
+        $var = array('lookup', $scope, $name);
+        return true;
+    }
+
+    // a string
+    function string(&$string, &$d = null) {
+        $s = $this->seek();
+        if ($this->literal('"', false)) {
+            $delim = '"';
+        } elseif ($this->literal("'", false)) {
+            $delim = "'";
+        } else {
+            return false;
+        }
+
+        if (!$this->to($delim, $string)) {
+            $this->seek($s);
+            return false;
+        }
+
+        $d = $delim;
+        return true;
+    }
+
+    /**
+     * Consume a number and optionally a unit.
+     * Can also consume a font shorthand if it is a simple case.
+     * $allowed restricts the types that are matched.
+     */
+    function unit(&$unit, $allowed = null) {
+        $simpleCase = $allowed == null;
+        if (!$allowed) $allowed = self::$units;
+
+        if ($this->match('(-?[0-9]*(\.)?[0-9]+)('.implode('|', $allowed).')?', $m, !$simpleCase)) {
+            if (!isset($m[3])) $m[3] = 'number';
+            $unit = array($m[3], $m[1]);
+
+            // check for size/height font unit.. should this even be here?
+            if ($simpleCase) {
+                $s = $this->seek();
+                if (!$this->inExp && $this->literal('/', false) && $this->unit($right, self::$units)) {
+                    $unit = array('keyword', $this->compileValue($unit).'/'.$this->compileValue($right));
+                } else {
+                    // get rid of whitespace
+                    $this->seek($s);
+                    $this->match('', $_);
+                }
+            }
+
+            return true;
+        }
+
+        return false;
+    }
+
+    // a # color
+    function color(&$out) {
+        $color = array('color');
+
+        if ($this->match('(#([0-9a-f]{6})|#([0-9a-f]{3}))', $m)) {
+            if (isset($m[3])) {
+                $num = $m[3];
+                $width = 16;
+            } else {
+                $num = $m[2];
+                $width = 256;
+            }
+
+            $num = hexdec($num);
+            foreach (array(3,2,1) as $i) {
+                $t = $num % $width;
+                $num /= $width;
+
+                $color[$i] = $t * (256/$width) + $t * floor(16/$width);
+            }
+
+            $out = $color;
+            return true;
+        }
+
+        return false;
+    }
+
+    // consume a list of property values delimited by ; and wrapped in ()
+    function argumentValues(&$args, $delim = ',') {
+        $s = $this->seek();
+        if (!$this->literal('(')) return false;
+
+        $values = array();
+        while (true) {
+            if ($this->expressionList($value)) $values[] = $value;
+            if (!$this->literal($delim)) break;
+            else {
+                if ($value == null) $values[] = null;
+                $value = null;
+            }
+        }
+
+        if (!$this->literal(')')) {
+            $this->seek($s);
+            return false;
+        }
+
+        $args = $values;
+        return true;
+    }
+
+    // consume an argument definition list surrounded by ()
+    // each argument is a variable name with optional value
+    function argumentDef(&$args, $delim = ',') {
+        $s = $this->seek();
+        if (!$this->literal('(')) return false;
+
+        $values = array();
+        while ($this->variable($vname)) {
+            $arg = array($vname);
+            if ($this->assign() && $this->expressionList($value)) {
+                $arg[] = $value;
+                // let the : slide if there is no value
+            }
+
+            $values[] = $arg;
+            if (!$this->literal($delim)) break;
+        }
+
+        if (!$this->literal(')')) {
+            $this->seek($s);
+            return false;
+        }
+
+        $args = $values;
+        return true;
+    }
+
+    // consume a list of tags
+    // this accepts a hanging delimiter
+    function tags(&$tags, $simple = false, $delim = ',') {
+        $tags = array();
+        while ($this->tag($tt, $simple)) {
+            $tags[] = $tt;
+            if (!$this->literal($delim)) break;
+        }
+        if (count($tags) == 0) return false;
+
+        return true;
+    }
+
+    // list of tags of specifying mixin path
+    // optionally separated by > (lazy, accepts extra >)
+    function mixinTags(&$tags) {
+        $s = $this->seek();
+        $tags = array();
+        while ($this->tag($tt, true)) {
+            $tags[] = $tt;
+            $this->literal(">");
+        }
+
+        if (count($tags) == 0) return false;
+
+        return true;
+    }
+
+    // a bracketed value (contained within in a tag definition)
+    function tagBracket(&$value) {
+        $s = $this->seek();
+        if ($this->literal('[') && $this->to(']', $c, true) && $this->literal(']', false)) {
+            $value = '['.$c.']';
+            // whitespace?
+            if ($this->match('', $_)) $value .= $_[0];
+
+            // escape parent selector
+            $value = str_replace($this->parentSelector, "&&", $value);
+            return true;
+        }
+
+        $this->seek($s);
+        return false;
+    }
+
+    // a single tag
+    function tag(&$tag, $simple = false) {
+        if ($simple)
+            $chars = '^,:;{}\][>\(\) "\'';
+        else
+            $chars = '^,;{}["\'';
+
+        $tag = '';
+        while ($this->tagBracket($first)) $tag .= $first;
+        while ($this->match('(['.$chars.'0-9]['.$chars.']*)', $m)) {
+            $tag .= $m[1];
+            if ($simple) break;
+
+            while ($this->tagBracket($brack)) $tag .= $brack;
+        }
+        $tag = trim($tag);
+        if ($tag == '') return false;
+
+        return true;
+    }
+
+    // a css function
+    function func(&$func) {
+        $s = $this->seek();
+
+        if ($this->match('(%|[\w\-_][\w\-_:\.]*)', $m) && $this->literal('(')) {
+            $fname = $m[1];
+            if ($fname == 'url') {
+                $this->to(')', $content, true);
+                $args = array('string', $content);
+            } else {
+                $args = array();
+                while (true) {
+                    $ss = $this->seek();
+                    if ($this->keyword($name) && $this->literal('=') && $this->expressionList($value)) {
+                        $args[] = array('list', '=', array(array('keyword', $name), $value));
+                    } else {
+                        $this->seek($ss);
+                        if ($this->expressionList($value)) {
+                            $args[] = $value;
+                        }
+                    }
+
+                    if (!$this->literal(',')) break;
+                }
+                $args = array('list', ',', $args);
+            }
+
+            if ($this->literal(')')) {
+                $func = array('function', $fname, $args);
+                return true;
+            }
+        }
+
+        $this->seek($s);
+        return false;
+    }
+
+    // consume a less variable
+    function variable(&$name) {
+        $s = $this->seek();
+        if ($this->literal($this->vPrefix, false) && $this->keyword($name)) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Consume an assignment operator
+     * Can optionally take a name that will be set to the current property name
+     */
+    function assign($name = null) {
+        if ($name) $this->currentProperty = $name;
+        return $this->literal(':') || $this->literal('=');
+    }
+
+    // consume a keyword
+    function keyword(&$word) {
+        if ($this->match('([\w_\-\*!"][\w\-_"]*)', $m)) {
+            $word = $m[1];
+            return true;
+        }
+        return false;
+    }
+
+    // consume an end of statement delimiter
+    function end() {
+        if ($this->literal(';'))
+            return true;
+        elseif ($this->count == strlen($this->buffer) || $this->buffer{$this->count} == '}') {
+            // if there is end of file or a closing block next then we don't need a ;
+            return true;
+        }
+        return false;
+    }
+
+    function compressList($items, $delim) {
+        if (count($items) == 1) return $items[0];
+        else return array('list', $delim, $items);
+    }
+
+    // just do a shallow propety merge, seems to be what lessjs does
+    function mergeBlock($target, $from) {
+        $target = clone $target;
+        $target->props = array_merge($target->props, $from->props);
+        return $target;
+    }
+
+    /**
+     * Recursively compiles a block.
+     * @param $block the block
+     * @param $parentTags the tags of the block that contained this one
+     *
+     * A block is analogous to a CSS block in most cases. A single less document
+     * is encapsulated in a block when parsed, but it does not have parent tags
+     * so all of it's children appear on the root level when compiled.
+     *
+     * Blocks are made up of props and children.
+     *
+     * Props are property instructions, array tuples which describe an action
+     * to be taken, eg. write a property, set a variable, mixin a block.
+     *
+     * The children of a block are just all the blocks that are defined within.
+     *
+     * Compiling the block involves pushing a fresh environment on the stack,
+     * and iterating through the props, compiling each one.
+     *
+     * See lessc::compileProp()
+     *
+     */
+    function compileBlock($block, $parent_tags = null) {
+        $isRoot = $parent_tags == null && $block->tags == null;
+
+        $indent = str_repeat($this->indentChar, $this->indentLevel);
+
+        if (!empty($block->no_multiply)) {
+            $special_block = true;
+            $this->indentLevel++;
+            $tags = array();
+        } else {
+            $special_block = false;
+            $tags = $this->multiplyTags($parent_tags, $block->tags);
+        }
+
+        $this->pushEnv();
+        $lines = array();
+        $blocks = array();
+        foreach ($block->props as $prop) {
+            $this->compileProp($prop, $block, $tags, $lines, $blocks);
+        }
+
+        $this->pop();
+
+        $nl = $isRoot ? "\n".$indent :
+            "\n".$indent.$this->indentChar;
+
+        ob_start();
+
+        if ($special_block) {
+            $this->indentLevel--;
+            if (isset($block->media)) {
+                echo "@media ".$block->media;
+            } elseif (isset($block->keyframes)) {
+                echo $block->tags[0]." ".
+                    $this->compileValue($this->reduce($block->keyframes));
+            } else {
+                list($name) = $block->tags;
+                echo $indent.$name;
+            }
+
+            echo ' {'.(count($lines) > 0 ? $nl : "\n");
+        }
+
+        // dump it
+        if (count($lines) > 0) {
+            if (!$special_block && !$isRoot) {
+                echo $indent.implode(", ", $tags);
+                if (count($lines) > 1) echo " {".$nl;
+                else echo " { ";
+            }
+
+            echo implode($nl, $lines);
+
+            if (!$special_block && !$isRoot) {
+                if (count($lines) > 1) echo "\n".$indent."}\n";
+                else echo " }\n";
+            } else echo "\n";
+        }
+
+        foreach ($blocks as $b) echo $b;
+
+        if ($special_block) {
+            echo $indent."}\n";
+        }
+
+        return ob_get_clean();
+    }
+
+
+    // find the fully qualified tags for a block and its parent's tags
+    function multiplyTags($parents, $current) {
+        if ($parents == null) return $current;
+
+        $tags = array();
+        foreach ($parents as $ptag) {
+            foreach ($current as $tag) {
+                // inject parent in place of parent selector, ignoring escaped valuews
+                $count = 0;
+                $parts = explode("&&", $tag);
+
+                foreach ($parts as $i => $chunk) {
+                    $parts[$i] = str_replace($this->parentSelector, $ptag, $chunk, $c);
+                    $count += $c;
+                }
+
+                $tag = implode("&", $parts);
+
+                if ($count > 0) {
+                    $tags[] = trim($tag);
+                } else {
+                    $tags[] = trim($ptag . ' ' . $tag);
+                }
+            }
+        }
+
+        return $tags;
+    }
+
+    // attempt to find block pointed at by path within search_in or its parent
+    function findBlock($search_in, $path, $seen=array()) {
+        if ($search_in == null) return null;
+        if (isset($seen[$search_in->id])) return null;
+        $seen[$search_in->id] = true;
+
+        $name = $path[0];
+
+        if (isset($search_in->children[$name])) {
+            $block = $search_in->children[$name];
+            if (count($path) == 1) {
+                return $block;
+            } else {
+                return $this->findBlock($block, array_slice($path, 1), $seen);
+            }
+        } else {
+            if ($search_in->parent === $search_in) return null;
+            return $this->findBlock($search_in->parent, $path, $seen);
+        }
+    }
+
+    // sets all argument names in $args to either the default value
+    // or the one passed in through $values
+    function zipSetArgs($args, $values) {
+        $i = 0;
+        $assigned_values = array();
+        foreach ($args as $a) {
+            if ($i < count($values) && !is_null($values[$i])) {
+                $value = $values[$i];
+            } elseif (isset($a[1])) {
+                $value = $a[1];
+            } else $value = null;
+
+            $value = $this->reduce($value);
+            $this->set($this->vPrefix.$a[0], $value);
+            $assigned_values[] = $value;
+            $i++;
+        }
+
+        // copy over any extra default args
+        for ($i = count($values); $i < count($assigned_values); $i++) {
+            $values[] = $assigned_values[$i];
+        }
+
+        $this->env->arguments = $values;
+    }
+
+    // compile a prop and update $lines or $blocks appropriately
+    function compileProp($prop, $block, $tags, &$_lines, &$_blocks) {
+        switch ($prop[0]) {
+        case 'assign':
+            list(, $name, $value) = $prop;
+            if ($name[0] == $this->vPrefix) {
+                $this->set($name, $this->reduce($value));
+            } else {
+                $_lines[] = "$name:".
+                    $this->compileValue($this->reduce($value)).";";
+            }
+            break;
+        case 'block':
+            list(, $child) = $prop;
+            $_blocks[] = $this->compileBlock($child, $tags);
+            break;
+        case 'mixin':
+            list(, $path, $args) = $prop;
+
+            $mixin = $this->findBlock($block, $path);
+            if (is_null($mixin)) {
+                // echo "failed to find block: ".implode(" > ", $path)."\n";
+                break; // throw error here??
+            }
+
+            $have_args = false;
+            if (isset($mixin->args)) {
+                $have_args = true;
+                $this->pushEnv();
+                $this->zipSetArgs($mixin->args, $args);
+            }
+
+            $old_parent = $mixin->parent;
+            $mixin->parent = $block;
+
+            foreach ($mixin->props as $sub_prop) {
+                $this->compileProp($sub_prop, $mixin, $tags, $_lines, $_blocks);
+            }
+
+            $mixin->parent = $old_parent;
+
+            if ($have_args) $this->pop();
+
+            break;
+        case 'raw':
+            $_lines[] = $prop[1];
+            break;
+        case 'import':
+            list(, $path) = $prop;
+            $this->addParsedFile($path);
+            $root = $this->createChild($path)->parseTree();
+
+            $root->parent = $block;
+            foreach ($root->props as $sub_prop) {
+                $this->compileProp($sub_prop, $root, $tags, $_lines, $_blocks);
+            }
+
+            // inject imported blocks into this block, local will overwrite import
+            $block->children = array_merge($root->children, $block->children);
+            break;
+        case 'charset':
+            list(, $value) = $prop;
+            $_lines[] = '@charset '.$this->compileValue($this->reduce($value)).';';
+            break;
+        default:
+            echo "unknown op: {$prop[0]}\n";
+            throw new exception();
+        }
+    }
+
+
+    /**
+     * Compiles a primitive value into a CSS property value.
+     *
+     * Values in lessphp are typed by being wrapped in arrays, their format is
+     * typically:
+     *
+     *     array(type, contents [, additional_contents]*)
+     *
+     * Will not work on non reduced values (expressions, variables, etc)
+     */
+    function compileValue($value) {
+        switch ($value[0]) {
+        case 'list':
+            // [1] - delimiter
+            // [2] - array of values
+            return implode($value[1], array_map(array($this, 'compileValue'), $value[2]));
+        case 'keyword':
+            // [1] - the keyword
+        case 'number':
+            // [1] - the number
+            return $value[1];
+        case 'string':
+            // [1] - contents of string (includes quotes)
+
+            // search for inline variables to replace
+            $replace = array();
+            if (preg_match_all('/'.$this->preg_quote($this->vPrefix).'\{([\w-_][0-9\w-_]*)\}/', $value[1], $m)) {
+                foreach ($m[1] as $name) {
+                    if (!isset($replace[$name]))
+                        $replace[$name] = $this->compileValue($this->reduce(array('variable', $this->vPrefix . $name)));
+                }
+            }
+
+            foreach ($replace as $var=>$val) {
+                if ($this->quoted($val)) {
+                    $val = substr($val, 1, -1);
+                }
+                $value[1] = str_replace($this->vPrefix. '{'.$var.'}', $val, $value[1]);
+            }
+
+            return $value[1];
+        case 'color':
+            // [1] - red component (either number for a %)
+            // [2] - green component
+            // [3] - blue component
+            // [4] - optional alpha component
+            if (count($value) == 5) { // rgba
+                return 'rgba('.$value[1].','.$value[2].','.$value[3].','.$value[4].')';
+            }
+            return sprintf("#%02x%02x%02x", $value[1], $value[2], $value[3]);
+        case 'function':
+            // [1] - function name
+            // [2] - some value representing arguments
+
+            // see if function evaluates to something else
+            $value = $this->reduce($value);
+            if ($value[0] == 'function') {
+                return $value[1].'('.$this->compileValue($value[2]).')';
+            }
+            else return $this->compileValue($value);
+        default: // assumed to be unit
+            return $value[1].$value[0];
+        }
+    }
+
+    function lib_rgbahex($color) {
+        if ($color[0] != 'color')
+            throw new exception("color expected for rgbahex");
+
+        return sprintf("#%02x%02x%02x%02x",
+            isset($color[4]) ? $color[4]*255 : 0,
+            $color[1],$color[2], $color[3]);
+    }
+
+    // utility func to unquote a string
+    function lib_e($arg) {
+        switch ($arg[0]) {
+            case "list":
+                $items = $arg[2];
+                if (isset($items[0])) {
+                    return $this->lib_e($items[0]);
+                }
+                return "";
+            case "string":
+                $str = $this->compileValue($arg);
+                return substr($str, 1, -1);
+            default:
+                return $this->compileValue($arg);
+        }
+    }
+
+    function lib__sprintf($args) {
+        if ($args[0] != "list") return $args;
+        $values = $args[2];
+        $source = $this->reduce(array_shift($values));
+        if ($source[0] != "string") {
+            return $source;
+        }
+
+        $str = $source[1];
+        $i = 0;
+        if (preg_match_all('/%[dsa]/', $str, $m)) {
+            foreach ($m[0] as $match) {
+                $val = isset($values[$i]) ? $this->reduce($values[$i]) : array('keyword', '');
+                $i++;
+                switch ($match[1]) {
+                case "s":
+                    if ($val[0] == "string") {
+                        $rep = substr($val[1], 1, -1);
+                        break;
+                    }
+                default:
+                    $rep = $this->compileValue($val);
+                }
+                $str = preg_replace('/'.$this->preg_quote($match).'/', $rep, $str, 1);
+            }
+        }
+
+        return array('string', $str);
+    }
+
+    function lib_floor($arg) {
+        return floor($arg[1]);
+    }
+
+    function lib_round($arg) {
+        return round($arg[1]);
+    }
+
+    // is a string surrounded in quotes? returns the quoting char if true
+    function quoted($s) {
+        if (preg_match('/^("|\').*?\1$/', $s, $m))
+            return $m[1];
+        else return false;
+    }
+
+    /**
+     * Helper function to get argurments for color functions
+     * accepts invalid input, non colors interpreted to black
+     */
+    function colorArgs($args) {
+        if ($args[0] != 'list' || count($args[2]) < 2) {
+            return array(array('color', 0, 0, 0));
+        }
+        list($color, $delta) = $args[2];
+        $color = $this->coerceColor($color);
+        if (is_null($color))
+            $color = array('color', 0, 0, 0);
+
+        $delta = floatval($delta[1]);
+
+        return array($color, $delta);
+    }
+
+    function lib_darken($args) {
+        list($color, $delta) = $this->colorArgs($args);
+
+        $hsl = $this->toHSL($color);
+        $hsl[3] = $this->clamp($hsl[3] - $delta, 100);
+        return $this->toRGB($hsl);
+    }
+
+    function lib_lighten($args) {
+        list($color, $delta) = $this->colorArgs($args);
+
+        $hsl = $this->toHSL($color);
+        $hsl[3] = $this->clamp($hsl[3] + $delta, 100);
+        return $this->toRGB($hsl);
+    }
+
+    function lib_saturate($args) {
+        list($color, $delta) = $this->colorArgs($args);
+
+        $hsl = $this->toHSL($color);
+        $hsl[2] = $this->clamp($hsl[2] + $delta, 100);
+        return $this->toRGB($hsl);
+    }
+
+    function lib_desaturate($args) {
+        list($color, $delta) = $this->colorArgs($args);
+
+        $hsl = $this->toHSL($color);
+        $hsl[2] = $this->clamp($hsl[2] - $delta, 100);
+        return $this->toRGB($hsl);
+    }
+
+    function lib_spin($args) {
+        list($color, $delta) = $this->colorArgs($args);
+
+        $hsl = $this->toHSL($color);
+        $hsl[1] = $this->clamp($hsl[1] + $delta, 360);
+        return $this->toRGB($hsl);
+    }
+
+    function lib_fadeout($args) {
+        list($color, $delta) = $this->colorArgs($args);
+        $color[4] = $this->clamp((isset($color[4]) ? $color[4] : 1) - $delta/100);
+        return $color;
+    }
+
+    function lib_fadein($args) {
+        list($color, $delta) = $this->colorArgs($args);
+        $color[4] = $this->clamp((isset($color[4]) ? $color[4] : 1) + $delta/100);
+        return $color;
+    }
+
+    function lib_hue($color) {
+        if ($color[0] != 'color') return 0;
+        $hsl = $this->toHSL($color);
+        return round($hsl[1]);
+    }
+
+    function lib_saturation($color) {
+        if ($color[0] != 'color') return 0;
+        $hsl = $this->toHSL($color);
+        return round($hsl[2]);
+    }
+
+    function lib_lightness($color) {
+        if ($color[0] != 'color') return 0;
+        $hsl = $this->toHSL($color);
+        return round($hsl[3]);
+    }
+
+    // get the alpha of a color
+    // defaults to 1 for non-colors or colors without an alpha
+    function lib_alpha($color) {
+        if ($color[0] != 'color') return 1;
+        return isset($color[4]) ? $color[4] : 1;
+    }
+
+    // set the alpha of the color
+    function lib_fade($args) {
+        list($color, $alpha) = $this->colorArgs($args);
+        $color[4] = $this->clamp($alpha / 100.0);
+        return $color;
+    }
+
+    function lib_percentage($number) {
+        return array('%', $number[1]*100);
+    }
+
+    // mixes two colors by weight
+    // mix(@color1, @color2, @weight);
+    // http://sass-lang.com/docs/yardoc/Sass/Script/Functions.html#mix-instance_method
+    function lib_mix($args) {
+        if ($args[0] != "list")
+            throw new exception("mix expects (color1, color2, weight)");
+
+        list($first, $second, $weight) = $args[2];
+        $first = $this->assertColor($first);
+        $second = $this->assertColor($second);
+
+        $first_a = $this->lib_alpha($first);
+        $second_a = $this->lib_alpha($second);
+        $weight = $weight[1] / 100.0;
+
+        $w = $weight * 2 - 1;
+        $a = $first_a - $second_a;
+
+        $w1 = (($w * $a == -1 ? $w : ($w + $a)/(1 + $w * $a)) + 1) / 2.0;
+        $w2 = 1.0 - $w1;
+
+        $new = array('color',
+            $w1 * $first[1] + $w2 * $second[1],
+            $w1 * $first[2] + $w2 * $second[2],
+            $w1 * $first[3] + $w2 * $second[3],
+        );
+
+        if ($first_a != 1.0 || $second_a != 1.0) {
+            $new[] = $first_a * $p + $second_a * ($p - 1);
+        }
+
+        return $this->fixColor($new);
+    }
+
+    function assertColor($value, $error = "expected color value") {
+        $color = $this->coerceColor($value);
+        if (is_null($color)) throw new exception($error);
+        return $color;
+    }
+
+    function toHSL($color) {
+        if ($color[0] == 'hsl') return $color;
+
+        $r = $color[1] / 255;
+        $g = $color[2] / 255;
+        $b = $color[3] / 255;
+
+        $min = min($r, $g, $b);
+        $max = max($r, $g, $b);
+
+        $L = ($min + $max) / 2;
+        if ($min == $max) {
+            $S = $H = 0;
+        } else {
+            if ($L < 0.5)
+                $S = ($max - $min)/($max + $min);
+            else
+                $S = ($max - $min)/(2.0 - $max - $min);
+
+            if ($r == $max) $H = ($g - $b)/($max - $min);
+            elseif ($g == $max) $H = 2.0 + ($b - $r)/($max - $min);
+            elseif ($b == $max) $H = 4.0 + ($r - $g)/($max - $min);
+
+        }
+
+        $out = array('hsl',
+            ($H < 0 ? $H + 6 : $H)*60,
+            $S*100,
+            $L*100,
+        );
+
+        if (count($color) > 4) $out[] = $color[4]; // copy alpha
+        return $out;
+    }
+
+    function toRGB_helper($comp, $temp1, $temp2) {
+        if ($comp < 0) $comp += 1.0;
+        elseif ($comp > 1) $comp -= 1.0;
+
+        if (6 * $comp < 1) return $temp1 + ($temp2 - $temp1) * 6 * $comp;
+        if (2 * $comp < 1) return $temp2;
+        if (3 * $comp < 2) return $temp1 + ($temp2 - $temp1)*((2/3) - $comp) * 6;
+
+        return $temp1;
+    }
+
+    /**
+     * Converts an hsl array into a color value in rgb.
+     * Expects H to be in range of 0 to 360, S and L in 0 to 100
+     */
+    function toRGB($color) {
+        if ($color == 'color') return $color;
+
+        $H = $color[1] / 360;
+        $S = $color[2] / 100;
+        $L = $color[3] / 100;
+
+        if ($S == 0) {
+            $r = $g = $b = $L;
+        } else {
+            $temp2 = $L < 0.5 ?
+                $L*(1.0 + $S) :
+                $L + $S - $L * $S;
+
+            $temp1 = 2.0 * $L - $temp2;
+
+            $r = $this->toRGB_helper($H + 1/3, $temp1, $temp2);
+            $g = $this->toRGB_helper($H, $temp1, $temp2);
+            $b = $this->toRGB_helper($H - 1/3, $temp1, $temp2);
+        }
+
+        $out = array('color', round($r*255), round($g*255), round($b*255));
+        if (count($color) > 4) $out[] = $color[4]; // copy alpha
+        return $out;
+    }
+
+    function clamp($v, $max = 1, $min = 0) {
+        return min($max, max($min, $v));
+    }
+
+    /**
+     * Convert the rgb, rgba, hsl color literals of function type
+     * as returned by the parser into values of color type.
+     */
+    function funcToColor($func) {
+        $fname = $func[1];
+        if ($func[2][0] != 'list') return false; // need a list of arguments
+        $rawComponents = $func[2][2];
+
+        if ($fname == 'hsl' || $fname == 'hsla') {
+            $hsl = array('hsl');
+            $i = 0;
+            foreach ($rawComponents as $c) {
+                $val = $this->reduce($c);
+                $val = isset($val[1]) ? floatval($val[1]) : 0;
+
+                if ($i == 0) $clamp = 360;
+                elseif ($i < 4) $clamp = 100;
+                else $clamp = 1;
+
+                $hsl[] = $this->clamp($val, $clamp);
+                $i++;
+            }
+
+            while (count($hsl) < 4) $hsl[] = 0;
+            return $this->toRGB($hsl);
+
+        } elseif ($fname == 'rgb' || $fname == 'rgba') {
+            $components = array();
+            $i = 1;
+            foreach ($rawComponents as $c) {
+                $c = $this->reduce($c);
+                if ($i < 4) {
+                    if ($c[0] == '%') $components[] = 255 * ($c[1] / 100);
+                    else $components[] = floatval($c[1]);
+                } elseif ($i == 4) {
+                    if ($c[0] == '%') $components[] = 1.0 * ($c[1] / 100);
+                    else $components[] = floatval($c[1]);
+                } else break;
+
+                $i++;
+            }
+            while (count($components) < 3) $components[] = 0;
+            array_unshift($components, 'color');
+            return $this->fixColor($components);
+        }
+
+        return false;
+    }
+
+    // reduce a delayed type to its final value
+    // dereference variables and solve equations
+    function reduce($var, $defaultValue = array('number', 0)) {
+        while (in_array($var[0], self::$dtypes)) {
+            if ($var[0] == 'list') {
+                foreach ($var[2] as &$value) $value = $this->reduce($value);
+                break;
+            } elseif ($var[0] == 'expression') {
+                $var = $this->evaluate($var[1], $var[2], $var[3]);
+            } elseif ($var[0] == 'variable') {
+                $var = $this->get($var[1]);
+            } elseif ($var[0] == 'lookup') {
+                // do accessor here....
+                $var = array('number', 0);
+            } elseif ($var[0] == 'function') {
+                $color = $this->funcToColor($var);
+                if ($color) $var = $color;
+                else {
+                    list($_, $name, $args) = $var;
+                    if ($name == "%") $name = "_sprintf";
+                    $f = isset($this->libFunctions[$name]) ?
+                        $this->libFunctions[$name] : array($this, 'lib_'.$name);
+
+                    if (is_callable($f)) {
+                        if ($args[0] == 'list')
+                            $args = $this->compressList($args[2], $args[1]);
+
+                        $var = call_user_func($f, $this->reduce($args));
+
+                        // convet to a typed value if the result is a php primitive
+                        if (is_numeric($var)) $var = array('number', $var);
+                        elseif (!is_array($var)) $var = array('keyword', $var);
+                    } else {
+                        // plain function, reduce args
+                        $var[2] = $this->reduce($var[2]);
+                    }
+                }
+                break; // done reducing after a function
+            } elseif ($var[0] == 'negative') {
+                $value = $this->reduce($var[1]);
+                if (is_numeric($value[1])) {
+                    $value[1] = -1*$value[1];
+                }
+                $var = $value;
+            }
+        }
+
+        return $var;
+    }
+
+    function coerceColor($value) {
+        switch($value[0]) {
+            case 'color': return $value;
+            case 'keyword':
+                $name = $value[1];
+                if (isset(self::$cssColors[$name])) {
+                    list($r, $g, $b) = explode(',', self::$cssColors[$name]);
+                    return array('color', $r, $g, $b);
+                }
+                return null;
+        }
+    }
+
+    // evaluate an expression
+    function evaluate($op, $left, $right) {
+        $left = $this->reduce($left);
+        $right = $this->reduce($right);
+
+        if ($left_color = $this->coerceColor($left)) {
+            $left = $left_color;
+        }
+
+        if ($right_color = $this->coerceColor($right)) {
+            $right = $right_color;
+        }
+
+        if ($left[0] == 'color' && $right[0] == 'color') {
+            $out = $this->op_color_color($op, $left, $right);
+            return $out;
+        }
+
+        if ($left[0] == 'color') {
+            return $this->op_color_number($op, $left, $right);
+        }
+
+        if ($right[0] == 'color') {
+            return $this->op_number_color($op, $left, $right);
+        }
+
+        // concatenate strings
+        if ($op == '+' && $left[0] == 'string') {
+            $append = $this->compileValue($right);
+            if ($this->quoted($append)) $append = substr($append, 1, -1);
+
+            $lhs = $this->compileValue($left);
+            if ($q = $this->quoted($lhs)) $lhs = substr($lhs, 1, -1);
+            if (!$q) $q = '';
+
+            return array('string', $q.$lhs.$append.$q);
+        }
+
+        if ($left[0] == 'keyword' || $right[0] == 'keyword' ||
+            $left[0] == 'string' || $right[0] == 'string')
+        {
+            // look for negative op
+            if ($op == '-') $right[1] = '-'.$right[1];
+            return array('keyword', $this->compileValue($left) .' '. $this->compileValue($right));
+        }
+
+        // default to number operation
+        return $this->op_number_number($op, $left, $right);
+    }
+
+    // make sure a color's components don't go out of bounds
+    function fixColor($c) {
+        foreach (range(1, 3) as $i) {
+            if ($c[$i] < 0) $c[$i] = 0;
+            if ($c[$i] > 255) $c[$i] = 255;
+            $c[$i] = floor($c[$i]);
+        }
+
+        return $c;
+    }
+
+    function op_number_color($op, $lft, $rgt) {
+        if ($op == '+' || $op = '*') {
+            return $this->op_color_number($op, $rgt, $lft);
+        }
+    }
+
+    function op_color_number($op, $lft, $rgt) {
+        if ($rgt[0] == '%') $rgt[1] /= 100;
+
+        return $this->op_color_color($op, $lft,
+            array_fill(1, count($lft) - 1, $rgt[1]));
+    }
+
+    function op_color_color($op, $left, $right) {
+        $out = array('color');
+        $max = count($left) > count($right) ? count($left) : count($right);
+        foreach (range(1, $max - 1) as $i) {
+            $lval = isset($left[$i]) ? $left[$i] : 0;
+            $rval = isset($right[$i]) ? $right[$i] : 0;
+            switch ($op) {
+            case '+':
+                $out[] = $lval + $rval;
+                break;
+            case '-':
+                $out[] = $lval - $rval;
+                break;
+            case '*':
+                $out[] = $lval * $rval;
+                break;
+            case '%':
+                $out[] = $lval % $rval;
+                break;
+            case '/':
+                if ($rval == 0) throw new exception("evaluate error: can't divide by zero");
+                $out[] = $lval / $rval;
+                break;
+            default:
+                throw new exception('evaluate error: color op number failed on op '.$op);
+            }
+        }
+        return $this->fixColor($out);
+    }
+
+    // operator on two numbers
+    function op_number_number($op, $left, $right) {
+        if ($right[0] == '%') $right[1] /= 100;
+
+        // figure out type
+        if ($right[0] == 'number' || $right[0] == '%') $type = $left[0];
+        else $type = $right[0];
+
+        $value = 0;
+        switch ($op) {
+        case '+':
+            $value = $left[1] + $right[1];
+            break;
+        case '*':
+            $value = $left[1] * $right[1];
+            break;
+        case '-':
+            $value = $left[1] - $right[1];
+            break;
+        case '%':
+            $value = $left[1] % $right[1];
+            break;
+        case '/':
+            if ($right[1] == 0) throw new exception('parse error: divide by zero');
+            $value = $left[1] / $right[1];
+            break;
+        default:
+            throw new exception('parse error: unknown number operator: '.$op);
+        }
+
+        return array($type, $value);
+    }
+
+
+    /* environment functions */
+
+    // push a new block on the stack, used for parsing
+    function pushBlock($tags) {
+        $b = new stdclass;
+        $b->parent = $this->env;
+
+        $b->id = self::$nextBlockId++;
+        $b->tags = $tags;
+        $b->props = array();
+        $b->children = array();
+
+        $this->env = $b;
+        return $b;
+    }
+
+    // push a block that doesn't multiply tags
+    function pushSpecialBlock($name) {
+        $b = $this->pushBlock(array($name));
+        $b->no_multiply = true;
+        return $b;
+    }
+
+    // used for compiliation variable state
+    function pushEnv() {
+        $e = new stdclass;
+        $e->parent = $this->env;
+
+        $this->store = array();
+
+        $this->env = $e;
+        return $e;
+    }
+
+    // pop something off the stack
+    function pop() {
+        $old = $this->env;
+        $this->env = $this->env->parent;
+        return $old;
+    }
+
+    // set something in the current env
+    function set($name, $value) {
+        $this->env->store[$name] = $value;
+    }
+
+    // append an property
+    function append($prop) {
+        $this->env->props[] = $prop;
+    }
+
+    // get the highest occurrence entry for a name
+    function get($name) {
+        $current = $this->env;
+
+        $is_arguments = $name == $this->vPrefix . 'arguments';
+        while ($current) {
+            if ($is_arguments && isset($current->arguments)) {
+                return array('list', ' ', $current->arguments);
+            }
+
+            if (isset($current->store[$name]))
+                return $current->store[$name];
+            else
+                $current = $current->parent;
+        }
+
+        return null;
+    }
+
+    /* raw parsing functions */
+
+    function literal($what, $eatWhitespace = true) {
+        // this is here mainly prevent notice from { } string accessor
+        if ($this->count >= strlen($this->buffer)) return false;
+
+        // shortcut on single letter
+        if (!$eatWhitespace && strlen($what) == 1) {
+            if ($this->buffer{$this->count} == $what) {
+                $this->count++;
+                return true;
+            }
+            else return false;
+        }
+
+        return $this->match($this->preg_quote($what), $m, $eatWhitespace);
+    }
+
+    function preg_quote($what) {
+        return preg_quote($what, '/');
+    }
+
+    // advance counter to next occurrence of $what
+    // $until - don't include $what in advance
+    function to($what, &$out, $until = false, $allowNewline = false) {
+        $validChars = $allowNewline ? "." : "[^\n]";
+        if (!$this->match('('.$validChars.'*?)'.$this->preg_quote($what), $m, !$until)) return false;
+        if ($until) $this->count -= strlen($what); // give back $what
+        $out = $m[1];
+        return true;
+    }
+
+    // try to match something on head of buffer
+    function match($regex, &$out, $eatWhitespace = true) {
+        $r = '/'.$regex.($eatWhitespace ? '\s*' : '').'/Ais';
+        if (preg_match($r, $this->buffer, $out, null, $this->count)) {
+            $this->count += strlen($out[0]);
+            return true;
+        }
+        return false;
+    }
+
+    // match something without consuming it
+    function peek($regex, &$out = null) {
+        $r = '/'.$regex.'/Ais';
+        $result =  preg_match($r, $this->buffer, $out, null, $this->count);
+
+        return $result;
+    }
+
+    // seek to a spot in the buffer or return where we are on no argument
+    function seek($where = null) {
+        if ($where === null) return $this->count;
+        else $this->count = $where;
+        return true;
+    }
+
+    /**
+     * Initialize state for a fresh parse
+     */
+    protected function prepareParser($buff) {
+        $this->env = null;
+        $this->expandStack = array();
+        $this->indentLevel = 0;
+        $this->count = 0;
+        $this->line = 1;
+
+        $this->buffer = $this->removeComments($buff);
+        $this->pushBlock(null); // set up global scope
+
+        // trim whitespace on head
+        if (preg_match('/^\s+/', $this->buffer, $m)) {
+            $this->line  += substr_count($m[0], "\n");
+            $this->buffer = ltrim($this->buffer);
+        }
+    }
+
+    // create a child parser (for compiling an import)
+    protected function createChild($fname) {
+        $less = new lessc($fname);
+        $less->importDir = $this->importDir;
+        $less->indentChar = $this->indentChar;
+        $less->compat = $this->compat;
+        return $less;
+    }
+
+    // parse code and return intermediate tree
+    public function parseTree($str = null) {
+        $this->prepareParser(is_null($str) ? $this->buffer : $str);
+        while (false !== $this->parseChunk());
+
+        if ($this->count != strlen($this->buffer))
+            $this->throwParseError();
+
+        if (!is_null($this->env->parent))
+            throw new exception('parse error: unclosed block');
+
+        $root = $this->env;
+        $this->env = null;
+        return $root;
+    }
+
+    // inject array of unparsed strings into environment as variables
+    protected function injectVariables($args) {
+        $this->pushEnv();
+        $parser = new lessc();
+        foreach ($args as $name => $str_value) {
+            if ($name{0} != '@') $name = '@'.$name;
+            $parser->count = 0;
+            $parser->buffer = (string)$str_value;
+            if (!$parser->propertyValue($value)) {
+                throw new Exception("failed to parse passed in variable $name: $str_value");
+            }
+
+            $this->set($name, $value);
+        }
+    }
+
+    // parse and compile buffer
+    function parse($str = null, $initial_variables = null) {
+        $locale = setlocale(LC_NUMERIC, 0);
+        setlocale(LC_NUMERIC, "C");
+        $root = $this->parseTree($str);
+
+        if ($initial_variables) $this->injectVariables($initial_variables);
+        $out = $this->compileBlock($root);
+        setlocale(LC_NUMERIC, $locale);
+        return $out;
+    }
+
+    function throwParseError($msg = 'parse error') {
+        $line = $this->line + substr_count(substr($this->buffer, 0, $this->count), "\n");
+        if (isset($this->fileName)) {
+            $loc = $this->fileName.' on line '.$line;
+        } else {
+            $loc = "line: ".$line;
+        }
+
+        if ($this->peek("(.*?)(\n|$)", $m))
+            throw new exception($msg.': failed at `'.$m[1].'` '.$loc);
+    }
+
+    /**
+     * Initialize any static state, can initialize parser for a file
+     */
+    function __construct($fname = null, $opts = null) {
+        if (!self::$operatorString) {
+            self::$operatorString =
+                '('.implode('|', array_map(array($this, 'preg_quote'),
+                    array_keys(self::$precedence))).')';
+        }
+
+        if ($fname) {
+            if (!is_file($fname)) {
+                throw new Exception('load error: failed to find '.$fname);
+            }
+            $pi = pathinfo($fname);
+
+            $this->fileName = $fname;
+            $this->importDir = $pi['dirname'].'/';
+            $this->buffer = file_get_contents($fname);
+
+            $this->addParsedFile($fname);
+        }
+    }
+
+    public function registerFunction($name, $func) {
+        $this->libFunctions[$name] = $func;
+    }
+
+    public function unregisterFunction($name) {
+        unset($this->libFunctions[$name]);
+    }
+
+    // remove comments from $text
+    // todo: make it work for all functions, not just url
+    function removeComments($text) {
+        $look = array(
+            'url(', '//', '/*', '"', "'"
+        );
+
+        $out = '';
+        $min = null;
+        $done = false;
+        while (true) {
+            // find the next item
+            foreach ($look as $token) {
+                $pos = strpos($text, $token);
+                if ($pos !== false) {
+                    if (!isset($min) || $pos < $min[1]) $min = array($token, $pos);
+                }
+            }
+
+            if (is_null($min)) break;
+
+            $count = $min[1];
+            $skip = 0;
+            $newlines = 0;
+            switch ($min[0]) {
+            case 'url(':
+                if (preg_match('/url\(.*?\)/', $text, $m, 0, $count))
+                    $count += strlen($m[0]) - strlen($min[0]);
+                break;
+            case '"':
+            case "'":
+                if (preg_match('/'.$min[0].'.*?'.$min[0].'/', $text, $m, 0, $count))
+                    $count += strlen($m[0]) - 1;
+                break;
+            case '//':
+                $skip = strpos($text, "\n", $count);
+                if ($skip === false) $skip = strlen($text) - $count;
+                else $skip -= $count;
+                break;
+            case '/*':
+                if (preg_match('/\/\*.*?\*\//s', $text, $m, 0, $count)) {
+                    $skip = strlen($m[0]);
+                    $newlines = substr_count($m[0], "\n");
+                }
+                break;
+            }
+
+            if ($skip == 0) $count += strlen($min[0]);
+
+            $out .= substr($text, 0, $count).str_repeat("\n", $newlines);
+            $text = substr($text, $count + $skip);
+
+            $min = null;
+        }
+
+        return $out.$text;
+    }
+
+    public function allParsedFiles() { return $this->allParsedFiles; }
+    protected function addParsedFile($file) {
+        $this->allParsedFiles[realpath($file)] = filemtime($file);
+    }
+
+
+    // compile to $in to $out if $in is newer than $out
+    // returns true when it compiles, false otherwise
+    public static function ccompile($in, $out) {
+        if (!is_file($out) || filemtime($in) > filemtime($out)) {
+            $less = new lessc($in);
+            file_put_contents($out, $less->parse());
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Execute lessphp on a .less file or a lessphp cache structure
+     *
+     * The lessphp cache structure contains information about a specific
+     * less file having been parsed. It can be used as a hint for future
+     * calls to determine whether or not a rebuild is required.
+     *
+     * The cache structure contains two important keys that may be used
+     * externally:
+     *
+     * compiled: The final compiled CSS
+     * updated: The time (in seconds) the CSS was last compiled
+     *
+     * The cache structure is a plain-ol' PHP associative array and can
+     * be serialized and unserialized without a hitch.
+     *
+     * @param mixed $in Input
+     * @param bool $force Force rebuild?
+     * @return array lessphp cache structure
+     */
+    public static function cexecute($in, $force = false) {
+
+        // assume no root
+        $root = null;
+
+        if (is_string($in)) {
+            $root = $in;
+        } elseif (is_array($in) and isset($in['root'])) {
+            if ($force or ! isset($in['files'])) {
+                // If we are forcing a recompile or if for some reason the
+                // structure does not contain any file information we should
+                // specify the root to trigger a rebuild.
+                $root = $in['root'];
+            } elseif (isset($in['files']) and is_array($in['files'])) {
+                foreach ($in['files'] as $fname => $ftime ) {
+                    if (!file_exists($fname) or filemtime($fname) > $ftime) {
+                        // One of the files we knew about previously has changed
+                        // so we should look at our incoming root again.
+                        $root = $in['root'];
+                        break;
+                    }
+                }
+            }
+        } else {
+            // TODO: Throw an exception? We got neither a string nor something
+            // that looks like a compatible lessphp cache structure.
+            return null;
+        }
+
+        if ($root !== null) {
+            // If we have a root value which means we should rebuild.
+            $less = new lessc($root);
+            $out = array();
+            $out['root'] = $root;
+            $out['compiled'] = $less->parse();
+            $out['files'] = $less->allParsedFiles();
+            $out['updated'] = time();
+            return $out;
+        } else {
+            // No changes, pass back the structure
+            // we were given initially.
+            return $in;
+        }
+
+    }
+
+    static protected $cssColors = array(
+        'aliceblue' => '240,248,255',
+        'antiquewhite' => '250,235,215',
+        'aqua' => '0,255,255',
+        'aquamarine' => '127,255,212',
+        'azure' => '240,255,255',
+        'beige' => '245,245,220',
+        'bisque' => '255,228,196',
+        'black' => '0,0,0',
+        'blanchedalmond' => '255,235,205',
+        'blue' => '0,0,255',
+        'blueviolet' => '138,43,226',
+        'brown' => '165,42,42',
+        'burlywood' => '222,184,135',
+        'cadetblue' => '95,158,160',
+        'chartreuse' => '127,255,0',
+        'chocolate' => '210,105,30',
+        'coral' => '255,127,80',
+        'cornflowerblue' => '100,149,237',
+        'cornsilk' => '255,248,220',
+        'crimson' => '220,20,60',
+        'cyan' => '0,255,255',
+        'darkblue' => '0,0,139',
+        'darkcyan' => '0,139,139',
+        'darkgoldenrod' => '184,134,11',
+        'darkgray' => '169,169,169',
+        'darkgreen' => '0,100,0',
+        'darkgrey' => '169,169,169',
+        'darkkhaki' => '189,183,107',
+        'darkmagenta' => '139,0,139',
+        'darkolivegreen' => '85,107,47',
+        'darkorange' => '255,140,0',
+        'darkorchid' => '153,50,204',
+        'darkred' => '139,0,0',
+        'darksalmon' => '233,150,122',
+        'darkseagreen' => '143,188,143',
+        'darkslateblue' => '72,61,139',
+        'darkslategray' => '47,79,79',
+        'darkslategrey' => '47,79,79',
+        'darkturquoise' => '0,206,209',
+        'darkviolet' => '148,0,211',
+        'deeppink' => '255,20,147',
+        'deepskyblue' => '0,191,255',
+        'dimgray' => '105,105,105',
+        'dimgrey' => '105,105,105',
+        'dodgerblue' => '30,144,255',
+        'firebrick' => '178,34,34',
+        'floralwhite' => '255,250,240',
+        'forestgreen' => '34,139,34',
+        'fuchsia' => '255,0,255',
+        'gainsboro' => '220,220,220',
+        'ghostwhite' => '248,248,255',
+        'gold' => '255,215,0',
+        'goldenrod' => '218,165,32',
+        'gray' => '128,128,128',
+        'green' => '0,128,0',
+        'greenyellow' => '173,255,47',
+        'grey' => '128,128,128',
+        'honeydew' => '240,255,240',
+        'hotpink' => '255,105,180',
+        'indianred' => '205,92,92',
+        'indigo' => '75,0,130',
+        'ivory' => '255,255,240',
+        'khaki' => '240,230,140',
+        'lavender' => '230,230,250',
+        'lavenderblush' => '255,240,245',
+        'lawngreen' => '124,252,0',
+        'lemonchiffon' => '255,250,205',
+        'lightblue' => '173,216,230',
+        'lightcoral' => '240,128,128',
+        'lightcyan' => '224,255,255',
+        'lightgoldenrodyellow' => '250,250,210',
+        'lightgray' => '211,211,211',
+        'lightgreen' => '144,238,144',
+        'lightgrey' => '211,211,211',
+        'lightpink' => '255,182,193',
+        'lightsalmon' => '255,160,122',
+        'lightseagreen' => '32,178,170',
+        'lightskyblue' => '135,206,250',
+        'lightslategray' => '119,136,153',
+        'lightslategrey' => '119,136,153',
+        'lightsteelblue' => '176,196,222',
+        'lightyellow' => '255,255,224',
+        'lime' => '0,255,0',
+        'limegreen' => '50,205,50',
+        'linen' => '250,240,230',
+        'magenta' => '255,0,255',
+        'maroon' => '128,0,0',
+        'mediumaquamarine' => '102,205,170',
+        'mediumblue' => '0,0,205',
+        'mediumorchid' => '186,85,211',
+        'mediumpurple' => '147,112,219',
+        'mediumseagreen' => '60,179,113',
+        'mediumslateblue' => '123,104,238',
+        'mediumspringgreen' => '0,250,154',
+        'mediumturquoise' => '72,209,204',
+        'mediumvioletred' => '199,21,133',
+        'midnightblue' => '25,25,112',
+        'mintcream' => '245,255,250',
+        'mistyrose' => '255,228,225',
+        'moccasin' => '255,228,181',
+        'navajowhite' => '255,222,173',
+        'navy' => '0,0,128',
+        'oldlace' => '253,245,230',
+        'olive' => '128,128,0',
+        'olivedrab' => '107,142,35',
+        'orange' => '255,165,0',
+        'orangered' => '255,69,0',
+        'orchid' => '218,112,214',
+        'palegoldenrod' => '238,232,170',
+        'palegreen' => '152,251,152',
+        'paleturquoise' => '175,238,238',
+        'palevioletred' => '219,112,147',
+        'papayawhip' => '255,239,213',
+        'peachpuff' => '255,218,185',
+        'peru' => '205,133,63',
+        'pink' => '255,192,203',
+        'plum' => '221,160,221',
+        'powderblue' => '176,224,230',
+        'purple' => '128,0,128',
+        'red' => '255,0,0',
+        'rosybrown' => '188,143,143',
+        'royalblue' => '65,105,225',
+        'saddlebrown' => '139,69,19',
+        'salmon' => '250,128,114',
+        'sandybrown' => '244,164,96',
+        'seagreen' => '46,139,87',
+        'seashell' => '255,245,238',
+        'sienna' => '160,82,45',
+        'silver' => '192,192,192',
+        'skyblue' => '135,206,235',
+        'slateblue' => '106,90,205',
+        'slategray' => '112,128,144',
+        'slategrey' => '112,128,144',
+        'snow' => '255,250,250',
+        'springgreen' => '0,255,127',
+        'steelblue' => '70,130,180',
+        'tan' => '210,180,140',
+        'teal' => '0,128,128',
+        'thistle' => '216,191,216',
+        'tomato' => '255,99,71',
+        'turquoise' => '64,224,208',
+        'violet' => '238,130,238',
+        'wheat' => '245,222,179',
+        'white' => '255,255,255',
+        'whitesmoke' => '245,245,245',
+        'yellow' => '255,255,0',
+        'yellowgreen' => '154,205,50'
+    );
 }
 
diff --git a/configs/phrozn.yml b/configs/phrozn.yml
index e06dab3..e37c3bb 100644
--- a/configs/phrozn.yml
+++ b/configs/phrozn.yml
@@ -3,7 +3,7 @@
 name: Phrozn
 summary: Static web-site generator for PHP.
 description: &desc Phrozn is extremely flexible static site generator for PHP.
-version: &ver "0.3.3"
+version: &ver "0.4.0"
 stability: beta
 author: Victor Farazdagi
 
diff --git a/package.xml b/package.xml
index 4767a11..0447016 100644
--- a/package.xml
+++ b/package.xml
@@ -13,11 +13,11 @@
   simple.square@gmail.com
   yes
  
- 2011-11-16
- 
+ 2012-01-24
+ 
  
-  0.3.3
-  0.3.3
+  0.4.0
+  0.4.0
  
  
   beta
@@ -395,10 +395,19 @@
      
      
     
+     
+      
+      
+      
+      
+      
+     
      
      
     
      
+     
+     
      
     
      
@@ -413,11 +422,14 @@
      
      
     
+     
      
      
     
     
+   
    
+   
    
    
  
diff --git a/skeleton/entries/about.twig b/skeleton/entries/about.twig
new file mode 100644
index 0000000..1175f18
--- /dev/null
+++ b/skeleton/entries/about.twig
@@ -0,0 +1,16 @@
+path: about/
+---
+
+ +
+
+

About Phrozn

+

Sample page..

+
+
+

Secondary content

+
+
+
diff --git a/skeleton/entries/demos/modal.twig b/skeleton/entries/demos/modal.twig new file mode 100644 index 0000000..d9064ee --- /dev/null +++ b/skeleton/entries/demos/modal.twig @@ -0,0 +1,18 @@ +

Modal Demo

+ + + + + diff --git a/skeleton/entries/demos/popup.twig b/skeleton/entries/demos/popup.twig new file mode 100644 index 0000000..72cab48 --- /dev/null +++ b/skeleton/entries/demos/popup.twig @@ -0,0 +1,2 @@ +

Popup Demo

+hover for popover diff --git a/skeleton/entries/demos/tabs.twig b/skeleton/entries/demos/tabs.twig new file mode 100644 index 0000000..40202ea --- /dev/null +++ b/skeleton/entries/demos/tabs.twig @@ -0,0 +1,34 @@ +

Tabs Demo

+ +
+
+

Raw denim you probably haven't heard of them jean shorts Austin. Nesciunt tofu stumptown aliqua, retro synth master cleanse. Mustache cliche tempor, williamsburg carles vegan helvetica. Reprehenderit butcher retro keffiyeh dreamcatcher synth. Cosby sweater eu banh mi, qui irure terry richardson ex squid. Aliquip placeat salvia cillum iphone. Seitan aliquip quis cardigan american apparel, butcher voluptate nisi qui.

+
+
+

Food truck fixie locavore, accusamus mcsweeney's marfa nulla single-origin coffee squid. Exercitation +1 labore velit, blog sartorial PBR leggings next level wes anderson artisan four loko farm-to-table craft beer twee. Qui photo booth letterpress, commodo enim craft beer mlkshk aliquip jean shorts ullamco ad vinyl cillum PBR. Homo nostrud organic, assumenda labore aesthetic magna delectus mollit. Keytar helvetica VHS salvia yr, vero magna velit sapiente labore stumptown. Vegan fanny pack odio cillum wes anderson 8-bit, sustainable jean shorts beard ut DIY ethical culpa terry richardson biodiesel. Art party scenester stumptown, tumblr butcher vero sint qui sapiente accusamus tattooed echo park.

+
+
+

Banksy do proident, brooklyn photo booth delectus sunt artisan sed organic exercitation eiusmod four loko. Quis tattooed iphone esse aliqua. Master cleanse vero fixie mcsweeney's. Ethical portland aute, irony food truck pitchfork lomo eu anim. Aesthetic blog DIY, ethical beard leggings tofu consequat whatever cardigan nostrud. Helvetica you probably haven't heard of them carles, marfa veniam occaecat lomo before they sold out in shoreditch scenester sustainable thundercats. Consectetur tofu craft beer, mollit brunch fap echo park pitchfork mustache dolor.

+
+
+

Sunt qui biodiesel mollit officia, fanny pack put a bird on it thundercats seitan squid ad wolf bicycle rights blog. Et aute readymade farm-to-table carles 8-bit, nesciunt nulla etsy adipisicing organic ea. Master cleanse mollit high life, next level Austin nesciunt american apparel twee mustache adipisicing reprehenderit hoodie portland irony. Aliqua tofu quinoa +1 commodo eiusmod. High life williamsburg cupidatat twee homo leggings. Four loko vinyl DIY consectetur nisi, marfa retro keffiyeh vegan. Fanny pack viral retro consectetur gentrify fap.

+
+
+

Etsy mixtape wayfarers, ethical wes anderson tofu before they sold out mcsweeney's organic lomo retro fanny pack lo-fi farm-to-table readymade. Messenger bag gentrify pitchfork tattooed craft beer, iphone skateboard locavore carles etsy salvia banksy hoodie helvetica. DIY synth PBR banksy irony. Leggings gentrify squid 8-bit cred pitchfork. Williamsburg banh mi whatever gluten-free, carles pitchfork biodiesel fixie etsy retro mlkshk vice blog. Scenester cred you probably haven't heard of them, vinyl craft beer blog stumptown. Pitchfork sustainable tofu synth chambray yr.

+
+
+

Trust fund seitan letterpress, keytar raw denim keffiyeh etsy art party before they sold out master cleanse gluten-free squid scenester freegan cosby sweater. Fanny pack portland seitan DIY, art party locavore wolf cliche high life echo park Austin. Cred vinyl keffiyeh DIY salvia PBR, banh mi before they sold out farm-to-table VHS viral locavore cosby sweater. Lomo wolf viral, mustache readymade thundercats keffiyeh craft beer marfa ethical. Wolf salvia freegan, sartorial keffiyeh echo park vegan.

+
+
diff --git a/skeleton/entries/demos/twipsy.twig b/skeleton/entries/demos/twipsy.twig new file mode 100644 index 0000000..c2ae3b7 --- /dev/null +++ b/skeleton/entries/demos/twipsy.twig @@ -0,0 +1,4 @@ +

Twipsy Demo

+
+

Tight pants next level keffiyeh you probably haven't heard of them. Photo booth beard raw denim letterpress vegan messenger bag stumptown. Farm-to-table seitan, mcsweeney's fixie sustainable quinoa 8-bit american apparel have a terry richardson vinyl chambray. Beard stumptown, cardigans banh mi lomo thundercats. Tofu biodiesel williamsburg marfa, four loko mcsweeney's cleanse vegan chambray. A really ironic artisan whatever keytar, scenester farm-to-table banksy Austin twitter handle freegan cred raw denim single-origin coffee viral.

+
diff --git a/skeleton/entries/index.twig b/skeleton/entries/index.twig index 4b977e7..4c83421 100644 --- a/skeleton/entries/index.twig +++ b/skeleton/entries/index.twig @@ -1,3 +1,27 @@ layout: default.twig --- -this is index page +
+ +
+
+

Main content

+ +
+ {% include 'demos/modal.twig'%} + +
+ {% include 'demos/twipsy.twig' %} + +
+ {% include 'demos/popup.twig' %} + +
+ {% include 'demos/tabs.twig' %} +
+
+

Secondary content

+
+
+
diff --git a/skeleton/layouts/default.twig b/skeleton/layouts/default.twig index a099f8b..9f9bead 100644 --- a/skeleton/layouts/default.twig +++ b/skeleton/layouts/default.twig @@ -1,11 +1,50 @@ - + - - My Webpage - - - -

My Webpage

- {{ content }} - - + + + Phrozn! + + + + + + + + + + + + + + + + + + + + + + + {% include 'layouts/topbar.twig'%} +
+ {{ content }} + {% include 'layouts/footer.twig'%} +
+ + + diff --git a/skeleton/layouts/footer.twig b/skeleton/layouts/footer.twig new file mode 100644 index 0000000..a152a6f --- /dev/null +++ b/skeleton/layouts/footer.twig @@ -0,0 +1,3 @@ +
+

© Company 2011

+
diff --git a/skeleton/layouts/topbar.twig b/skeleton/layouts/topbar.twig new file mode 100644 index 0000000..c0efd2a --- /dev/null +++ b/skeleton/layouts/topbar.twig @@ -0,0 +1,36 @@ + diff --git a/skeleton/styles/bootstrap.less b/skeleton/styles/bootstrap.less new file mode 100644 index 0000000..7e58bd4 --- /dev/null +++ b/skeleton/styles/bootstrap.less @@ -0,0 +1,2304 @@ +/*! + * Bootstrap @VERSION + * + * Copyright 2011 Twitter, Inc + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Designed and built with all the love in the world @twitter by @mdo and @fat. + * Date: @DATE + */ + +// CSS Reset + +/* Reset.less + * Props to Eric Meyer (meyerweb.com) for his CSS reset file. We're using an adapted version here that cuts out some of the reset HTML elements we will never need here (i.e., dfn, samp, etc). + * */ + + +// ERIC MEYER RESET + +html, body { margin: 0; padding: 0; } +h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, cite, code, del, dfn, em, img, q, s, samp, small, strike, strong, sub, sup, tt, var, dd, dl, dt, li, ol, ul, fieldset, form, label, legend, button, table, caption, tbody, tfoot, thead, tr, th, td { margin: 0; padding: 0; border: 0; font-weight: normal; font-style: normal; font-size: 100%; line-height: 1; font-family: inherit; } +table { border-collapse: collapse; border-spacing: 0; } +ol, ul { list-style: none; } +q:before, q:after, blockquote:before, blockquote:after { content: ""; } + + +// Normalize.css +// Pulling in select resets form the normalize.css project + +// Display in IE6-9 and FF3 +// Source: http://github.com/necolas/normalize.css +html { + overflow-y: scroll; + font-size: 100%; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; +} +// Focus states +a:focus { + outline: thin dotted; +} +// Hover & Active +a:hover, +a:active { + outline: 0; +} + +// Display in IE6-9 and FF3 +// Source: http://github.com/necolas/normalize.css +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +nav, +section { + display: block; +} + +// Display block in IE6-9 and FF3 +// Source: http://github.com/necolas/normalize.css +audio, +canvas, +video { + display: inline-block; + *display: inline; + *zoom: 1; +} + +// Prevents modern browsers from displaying 'audio' without controls +// Source: http://github.com/necolas/normalize.css +audio:not([controls]) { + display: none; +} + +// Prevents sub and sup affecting line-height in all browsers +// Source: http://github.com/necolas/normalize.css +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} +sup { + top: -0.5em; +} +sub { + bottom: -0.25em; +} + +// Img border in a's and image quality +// Source: http://github.com/necolas/normalize.css +img { + border: 0; + -ms-interpolation-mode: bicubic; +} + +// Forms +// Source: http://github.com/necolas/normalize.css + +// Font size in all browsers, margin changes, misc consistency +button, +input, +select, +textarea { + font-size: 100%; + margin: 0; + vertical-align: baseline; + *vertical-align: middle; +} +button, +input { + line-height: normal; // FF3/4 have !important on line-height in UA stylesheet + *overflow: visible; // Inner spacing ie IE6/7 +} +button::-moz-focus-inner, +input::-moz-focus-inner { // Inner padding and border oddities in FF3/4 + border: 0; + padding: 0; +} +button, +input[type="button"], +input[type="reset"], +input[type="submit"] { + cursor: pointer; // Cursors on all buttons applied consistently + -webkit-appearance: button; // Style clicable inputs in iOS +} +input[type="search"] { // Appearance in Safari/Chrome + -webkit-appearance: textfield; + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; +} +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; // Inner-padding issues in Chrome OSX, Safari 5 +} +textarea { + overflow: auto; // Remove vertical scrollbar in IE6-9 + vertical-align: top; // Readability and alignment cross-browser +} +// Core variables and mixins +/* Variables.less + * Variables to customize the look and feel of Bootstrap + * */ + + +// Links +@linkColor: #0069d6; +@linkColorHover: darken(@linkColor, 15); + +// Grays +@black: #000; +@grayDark: lighten(@black, 25%); +@gray: lighten(@black, 50%); +@grayLight: lighten(@black, 75%); +@grayLighter: lighten(@black, 90%); +@white: #fff; + +// Accent Colors +@blue: #049CDB; +@blueDark: #0064CD; +@green: #46a546; +@red: #9d261d; +@yellow: #ffc40d; +@orange: #f89406; +@pink: #c3325f; +@purple: #7a43b6; + +// Baseline grid +@basefont: 13px; +@baseline: 18px; + +// Griditude +// Modify the grid styles in mixins.less +@gridColumns: 16; +@gridColumnWidth: 40px; +@gridGutterWidth: 20px; +@extraSpace: (@gridGutterWidth * 2); // For our grid calculations +@siteWidth: (@gridColumns * @gridColumnWidth) + (@gridGutterWidth * (@gridColumns - 1)); + +// Color Scheme +// Use this to roll your own color schemes if you like (unused by Bootstrap by default) +@baseColor: @blue; // Set a base color +@complement: spin(@baseColor, 180); // Determine a complementary color +@split1: spin(@baseColor, 158); // Split complements +@split2: spin(@baseColor, -158); +@triad1: spin(@baseColor, 135); // Triads colors +@triad2: spin(@baseColor, -135); +@tetra1: spin(@baseColor, 90); // Tetra colors +@tetra2: spin(@baseColor, -90); +@analog1: spin(@baseColor, 22); // Analogs colors +@analog2: spin(@baseColor, -22); + + + +// More variables coming soon: +// - @basefont to @baseFontSize +// - @baseline to @baseLineHeight +// - @baseFontFamily +// - @primaryButtonColor +// - anything else? File an issue on GitHub +// Clearfix for clearing floats like a boss h5bp.com/q +.clearfix() { + zoom: 1; + &:before, + &:after { + display: table; + content: ""; + zoom: 1; + } + &:after { + clear: both; + } +} + +// Center-align a block level element +.center-block() { + display: block; + margin-left: auto; + margin-right: auto; +} + +// Sizing shortcuts +.size(@height: 5px, @width: 5px) { + height: @height; + width: @width; +} +.square(@size: 5px) { + .size(@size, @size); +} + +// Input placeholder text +.placeholder(@color: @grayLight) { + :-moz-placeholder { + color: @color; + } + ::-webkit-input-placeholder { + color: @color; + } +} + +// Font Stacks +#font { + .shorthand(@weight: normal, @size: 14px, @lineHeight: 20px) { + font-size: @size; + font-weight: @weight; + line-height: @lineHeight; + } + .sans-serif(@weight: normal, @size: 14px, @lineHeight: 20px) { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: @size; + font-weight: @weight; + line-height: @lineHeight; + } + .serif(@weight: normal, @size: 14px, @lineHeight: 20px) { + font-family: "Georgia", Times New Roman, Times, serif; + font-size: @size; + font-weight: @weight; + line-height: @lineHeight; + } + .monospace(@weight: normal, @size: 12px, @lineHeight: 20px) { + font-family: "Monaco", Courier New, monospace; + font-size: @size; + font-weight: @weight; + line-height: @lineHeight; + } +} + +// Grid System +.fixed-container() { + width: @siteWidth; + margin-left: auto; + margin-right: auto; + .clearfix(); +} +.columns(@columnSpan: 1) { + width: (@gridColumnWidth * @columnSpan) + (@gridGutterWidth * (@columnSpan - 1)); +} +.offset(@columnOffset: 1) { + margin-left: (@gridColumnWidth * @columnOffset) + (@gridGutterWidth * (@columnOffset - 1)) + @extraSpace; +} +// Necessary grid styles for every column to make them appear next to each other horizontally +.gridColumn() { + display: inline; + float: left; + margin-left: @gridGutterWidth; +} +// makeColumn can be used to mark any element (e.g., .content-primary) as a column without changing markup to .span something +.makeColumn(@columnSpan: 1) { + .gridColumn(); + .columns(@columnSpan); +} + +// Border Radius +.border-radius(@radius: 5px) { + -webkit-border-radius: @radius; + -moz-border-radius: @radius; + border-radius: @radius; +} + +// Drop shadows +.box-shadow(@shadow: 0 1px 3px rgba(0,0,0,.25)) { + -webkit-box-shadow: @shadow; + -moz-box-shadow: @shadow; + box-shadow: @shadow; +} + +// Transitions +.transition(@transition) { + -webkit-transition: @transition; + -moz-transition: @transition; + -ms-transition: @transition; + -o-transition: @transition; + transition: @transition; +} + +// Background clipping +.background-clip(@clip) { + -webkit-background-clip: @clip; + -moz-background-clip: @clip; + background-clip: @clip; +} + +// CSS3 Content Columns +.content-columns(@columnCount, @columnGap: 20px) { + -webkit-column-count: @columnCount; + -moz-column-count: @columnCount; + column-count: @columnCount; + -webkit-column-gap: @columnGap; + -moz-column-gap: @columnGap; + column-gap: @columnGap; +} + +// Make any element resizable for prototyping +.resizable(@direction: both) { + resize: @direction; // Options are horizontal, vertical, both + overflow: auto; // Safari fix +} + +// Add an alphatransparency value to any background or border color (via Elyse Holladay) +#translucent { + .background(@color: @white, @alpha: 1) { + background-color: hsla(hue(@color), saturation(@color), lightness(@color), @alpha); + } + .border(@color: @white, @alpha: 1) { + border-color: hsla(hue(@color), saturation(@color), lightness(@color), @alpha); + background-clip: padding-box; + } +} + +// Gradient Bar Colors for buttons and allerts +.gradientBar(@primaryColor, @secondaryColor) { + #gradient > .vertical(@primaryColor, @secondaryColor); + text-shadow: 0 -1px 0 rgba(0,0,0,.25); + border-color: @secondaryColor @secondaryColor darken(@secondaryColor, 15%); + border-color: rgba(0,0,0,.1) rgba(0,0,0,.1) fadein(rgba(0,0,0,.1), 15%); +} + +// Gradients +#gradient { + .horizontal (@startColor: #555, @endColor: #333) { + background-color: @endColor; + background-repeat: repeat-x; + background-image: -khtml-gradient(linear, left top, right top, from(@startColor), to(@endColor)); // Konqueror + background-image: -moz-linear-gradient(left, @startColor, @endColor); // FF 3.6+ + background-image: -ms-linear-gradient(left, @startColor, @endColor); // IE10 + background-image: -webkit-gradient(linear, left top, right top, color-stop(0%, @startColor), color-stop(100%, @endColor)); // Safari 4+, Chrome 2+ + background-image: -webkit-linear-gradient(left, @startColor, @endColor); // Safari 5.1+, Chrome 10+ + background-image: -o-linear-gradient(left, @startColor, @endColor); // Opera 11.10 + background-image: linear-gradient(left, @startColor, @endColor); // Le standard + filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)",@startColor,@endColor)); // IE9 and down + } + .vertical (@startColor: #555, @endColor: #333) { + background-color: @endColor; + background-repeat: repeat-x; + background-image: -khtml-gradient(linear, left top, left bottom, from(@startColor), to(@endColor)); // Konqueror + background-image: -moz-linear-gradient(top, @startColor, @endColor); // FF 3.6+ + background-image: -ms-linear-gradient(top, @startColor, @endColor); // IE10 + background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, @startColor), color-stop(100%, @endColor)); // Safari 4+, Chrome 2+ + background-image: -webkit-linear-gradient(top, @startColor, @endColor); // Safari 5.1+, Chrome 10+ + background-image: -o-linear-gradient(top, @startColor, @endColor); // Opera 11.10 + background-image: linear-gradient(top, @startColor, @endColor); // The standard + filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",@startColor,@endColor)); // IE9 and down + } + .directional (@startColor: #555, @endColor: #333, @deg: 45deg) { + background-color: @endColor; + background-repeat: repeat-x; + background-image: -moz-linear-gradient(@deg, @startColor, @endColor); // FF 3.6+ + background-image: -ms-linear-gradient(@deg, @startColor, @endColor); // IE10 + background-image: -webkit-linear-gradient(@deg, @startColor, @endColor); // Safari 5.1+, Chrome 10+ + background-image: -o-linear-gradient(@deg, @startColor, @endColor); // Opera 11.10 + background-image: linear-gradient(@deg, @startColor, @endColor); // The standard + } + .vertical-three-colors(@startColor: #00b3ee, @midColor: #7a43b6, @colorStop: 50%, @endColor: #c3325f) { + background-color: @endColor; + background-repeat: no-repeat; + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(@startColor), color-stop(@colorStop, @midColor), to(@endColor)); + background-image: -webkit-linear-gradient(@startColor, @midColor @colorStop, @endColor); + background-image: -moz-linear-gradient(top, @startColor, @midColor @colorStop, @endColor); + background-image: -ms-linear-gradient(@startColor, @midColor @colorStop, @endColor); + background-image: -o-linear-gradient(@startColor, @midColor @colorStop, @endColor); + background-image: linear-gradient(@startColor, @midColor @colorStop, @endColor); + filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",@startColor,@endColor)); // IE9 and down, gets no color-stop at all for proper fallback + } +} + +// Reset filters for IE +.reset-filter() { + filter: e(%("progid:DXImageTransform.Microsoft.gradient(enabled = false)")); +} + +// Opacity +.opacity(@opacity: 100) { + filter: e(%("alpha(opacity=%d)", @opacity)); + -khtml-opacity: @opacity / 100; + -moz-opacity: @opacity / 100; + opacity: @opacity / 100; +} + +// Grid system and page structure +/* + * Scaffolding + * Basic and global styles for generating a grid system, structural layout, and page templates + * */ + + +// STRUCTURAL LAYOUT + +body { + background-color: @white; + margin: 0; + #font > .sans-serif(normal,@basefont,@baseline); + color: @grayDark; +} + +// Container (centered, fixed-width layouts) +.container { + .fixed-container(); +} + +// Fluid layouts (left aligned, with sidebar, min- & max-width content) +.container-fluid { + position: relative; + min-width: 940px; + padding-left: 20px; + padding-right: 20px; + .clearfix(); + > .sidebar { + position: absolute; + top: 0; + left: 20px; + width: 220px; + } + // TODO in v2: rename this and .popover .content to be more specific + > .content { + margin-left: 240px; + } +} + + +// BASE STYLES + +// Links +a { + color: @linkColor; + text-decoration: none; + line-height: inherit; + font-weight: inherit; + &:hover { + color: @linkColorHover; + text-decoration: underline; + } +} + +// Quick floats +.pull-right { + float: right; +} +.pull-left { + float: left; +} + +// Toggling content +.hide { + display: none; +} +.show { + display: block; +} + + +// GRID SYSTEM +// To customize the grid system, bring up the variables.less file and change the column count, size, and gutter there + +.row { + .clearfix(); + margin-left: -@gridGutterWidth; +} + +// Find all .span# classes within .row and give them the necessary properties for grid columns (supported by all browsers back to IE7) +// Credit to @dhg for the idea +.row > [class*="span"] { + .gridColumn(); +} + +// Default columns +.span1 { .columns(1); } +.span2 { .columns(2); } +.span3 { .columns(3); } +.span4 { .columns(4); } +.span5 { .columns(5); } +.span6 { .columns(6); } +.span7 { .columns(7); } +.span8 { .columns(8); } +.span9 { .columns(9); } +.span10 { .columns(10); } +.span11 { .columns(11); } +.span12 { .columns(12); } +.span13 { .columns(13); } +.span14 { .columns(14); } +.span15 { .columns(15); } +.span16 { .columns(16); } + +// For optional 24-column grid +.span17 { .columns(17); } +.span18 { .columns(18); } +.span19 { .columns(19); } +.span20 { .columns(20); } +.span21 { .columns(21); } +.span22 { .columns(22); } +.span23 { .columns(23); } +.span24 { .columns(24); } + +// Offset column options +.row { + > .offset1 { .offset(1); } + > .offset2 { .offset(2); } + > .offset3 { .offset(3); } + > .offset4 { .offset(4); } + > .offset5 { .offset(5); } + > .offset6 { .offset(6); } + > .offset7 { .offset(7); } + > .offset8 { .offset(8); } + > .offset9 { .offset(9); } + > .offset10 { .offset(10); } + > .offset11 { .offset(11); } + > .offset12 { .offset(12); } +} + +// Unique column sizes for 16-column grid +.span-one-third { width: 300px; } +.span-two-thirds { width: 620px; } +.row { + > .offset-one-third { margin-left: 340px; } + > .offset-two-thirds { margin-left: 660px; } +} + +// Styled patterns and elements +/* + * Tables.less + * Tables for, you guessed it, tabular data + * */ + + +// BASELINE STYLES + +table { + width: 100%; + margin-bottom: @baseline; + padding: 0; + font-size: @basefont; + border-collapse: collapse; + th, + td { + padding: 10px 10px 9px; + line-height: @baseline; + text-align: left; + } + th { + padding-top: 9px; + font-weight: bold; + vertical-align: middle; + } + td { + vertical-align: top; + border-top: 1px solid #ddd; + } + // When scoped to row, fix th in tbody + tbody th { + border-top: 1px solid #ddd; + vertical-align: top; + } +} + + +// CONDENSED VERSION +.condensed-table { + th, + td { + padding: 5px 5px 4px; + } +} + + +// BORDERED VERSION + +.bordered-table { + border: 1px solid #ddd; + border-collapse: separate; // Done so we can round those corners! + *border-collapse: collapse; /* IE7, collapse table to remove spacing */ + .border-radius(4px); + th + th, + td + td, + th + td { + border-left: 1px solid #ddd; + } + thead tr:first-child th:first-child, + tbody tr:first-child td:first-child { + .border-radius(4px 0 0 0); + } + thead tr:first-child th:last-child, + tbody tr:first-child td:last-child { + .border-radius(0 4px 0 0); + } + tbody tr:last-child td:first-child { + .border-radius(0 0 0 4px); + } + tbody tr:last-child td:last-child { + .border-radius(0 0 4px 0); + } +} + + +// TABLE CELL SIZES + +// This is a duplication of the main grid .columns() mixin, but subtracts 20px to account for input padding and border +.tableColumns(@columnSpan: 1) { + width: ((@gridColumnWidth - 20) * @columnSpan) + ((@gridColumnWidth - 20) * (@columnSpan - 1)); +} +table { + // Default columns + .span1 { .tableColumns(1); } + .span2 { .tableColumns(2); } + .span3 { .tableColumns(3); } + .span4 { .tableColumns(4); } + .span5 { .tableColumns(5); } + .span6 { .tableColumns(6); } + .span7 { .tableColumns(7); } + .span8 { .tableColumns(8); } + .span9 { .tableColumns(9); } + .span10 { .tableColumns(10); } + .span11 { .tableColumns(11); } + .span12 { .tableColumns(12); } + .span13 { .tableColumns(13); } + .span14 { .tableColumns(14); } + .span15 { .tableColumns(15); } + .span16 { .tableColumns(16); } +} + + +// ZEBRA-STRIPING + +// Default zebra-stripe styles (alternating gray and transparent backgrounds) +.zebra-striped { + tbody { + tr:nth-child(odd) td, + tr:nth-child(odd) th { + background-color: #f9f9f9; + } + tr:hover td, + tr:hover th { + background-color: #f5f5f5; + } + } +} + +table { + // Tablesorting styles w/ jQuery plugin + .header { + cursor: pointer; + &:after { + content: ""; + float: right; + margin-top: 7px; + border-width: 0 4px 4px; + border-style: solid; + border-color: #000 transparent; + visibility: hidden; + } + } + // Style the sorted column headers (THs) + .headerSortUp, + .headerSortDown { + background-color: rgba(141,192,219,.25); + text-shadow: 0 1px 1px rgba(255,255,255,.75); + } + // Style the ascending (reverse alphabetical) column header + .header:hover { + &:after { + visibility:visible; + } + } + // Style the descending (alphabetical) column header + .headerSortDown, + .headerSortDown:hover { + &:after { + visibility:visible; + .opacity(60); + } + } + // Style the ascending (reverse alphabetical) column header + .headerSortUp { + &:after { + border-bottom: none; + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-top: 4px solid #000; + visibility:visible; + .box-shadow(none); //can't add boxshadow to downward facing arrow :( + .opacity(60); + } + } + // Blue Table Headings + .blue { + color: @blue; + border-bottom-color: @blue; + } + .headerSortUp.blue, + .headerSortDown.blue { + background-color: lighten(@blue, 40%); + } + // Green Table Headings + .green { + color: @green; + border-bottom-color: @green; + } + .headerSortUp.green, + .headerSortDown.green { + background-color: lighten(@green, 40%); + } + // Red Table Headings + .red { + color: @red; + border-bottom-color: @red; + } + .headerSortUp.red, + .headerSortDown.red { + background-color: lighten(@red, 50%); + } + // Yellow Table Headings + .yellow { + color: @yellow; + border-bottom-color: @yellow; + } + .headerSortUp.yellow, + .headerSortDown.yellow { + background-color: lighten(@yellow, 40%); + } + // Orange Table Headings + .orange { + color: @orange; + border-bottom-color: @orange; + } + .headerSortUp.orange, + .headerSortDown.orange { + background-color: lighten(@orange, 40%); + } + // Purple Table Headings + .purple { + color: @purple; + border-bottom-color: @purple; + } + .headerSortUp.purple, + .headerSortDown.purple { + background-color: lighten(@purple, 40%); + } +} +/* Forms.less + * Base styles for various input types, form layouts, and states + * */ + + +// FORM STYLES + +form { + margin-bottom: @baseline; +} + +// Groups of fields with labels on top (legends) +fieldset { + margin-bottom: @baseline; + padding-top: @baseline; + legend { + display: block; + padding-left: 150px; + font-size: @basefont * 1.5; + line-height: 1; + color: @grayDark; + *padding: 0 0 5px 145px; /* IE6-7 */ + *line-height: 1.5; /* IE6-7 */ + } +} + +// Parent element that clears floats and wraps labels and fields together +form .clearfix { + margin-bottom: @baseline; + .clearfix() +} + +// Set font for forms +label, +input, +select, +textarea { + #font > .sans-serif(normal,13px,normal); +} + +// Float labels left +label { + padding-top: 6px; + font-size: @basefont; + line-height: @baseline; + float: left; + width: 130px; + text-align: right; + color: @grayDark; +} + +// Shift over the inside div to align all label's relevant content +form .input { + margin-left: 150px; +} + +// Checkboxs and radio buttons +input[type=checkbox], +input[type=radio] { + cursor: pointer; +} + +// Inputs, Textareas, Selects +input, +textarea, +select, +.uneditable-input { + display: inline-block; + width: 210px; + height: @baseline; + padding: 4px; + font-size: @basefont; + line-height: @baseline; + color: @gray; + border: 1px solid #ccc; + .border-radius(3px); +} + +// remove padding from select +select { + padding: initial; +} + +// mini reset for non-html5 file types +input[type=checkbox], +input[type=radio] { + width: auto; + height: auto; + padding: 0; + margin: 3px 0; + *margin-top: 0; /* IE6-7 */ + line-height: normal; + border: none; +} + +input[type=file] { + background-color: @white; + padding: initial; + border: initial; + line-height: initial; + .box-shadow(none); +} + +input[type=button], +input[type=reset], +input[type=submit] { + width: auto; + height: auto; +} + +select, +input[type=file] { + height: @baseline * 1.5; // In IE7, the height of the select element cannot be changed by height, only font-size + *height: auto; // Reset for IE7 + line-height: @baseline * 1.5; + *margin-top: 4px; /* For IE7, add top margin to align select with labels */ +} + +// Make multiple select elements height not fixed +select[multiple] { + height: inherit; + background-color: @white; // Fixes Chromium bug of unreadable items +} + +textarea { + height: auto; +} + +// For text that needs to appear as an input but should not be an input +.uneditable-input { + background-color: @white; + display: block; + border-color: #eee; + .box-shadow(inset 0 1px 2px rgba(0,0,0,.025)); + cursor: not-allowed; +} + +// Placeholder text gets special styles; can't be bundled together though for some reason +:-moz-placeholder { + color: @grayLight; +} +::-webkit-input-placeholder { + color: @grayLight; +} + +// Focus states +input, +textarea { + @transition: border linear .2s, box-shadow linear .2s; + .transition(@transition); + .box-shadow(inset 0 1px 3px rgba(0,0,0,.1)); +} +input:focus, +textarea:focus { + outline: 0; + border-color: rgba(82,168,236,.8); + @shadow: inset 0 1px 3px rgba(0,0,0,.1), 0 0 8px rgba(82,168,236,.6); + .box-shadow(@shadow); +} +input[type=file]:focus, +input[type=checkbox]:focus, +select:focus { + .box-shadow(none); // override for file inputs + outline: 1px dotted #666; // Selet elements don't get box-shadow styles, so instead we do outline +} + + +// FORM FIELD FEEDBACK STATES + +// Mixin for form field states +.formFieldState(@textColor: #555, @borderColor: #ccc, @backgroundColor: #f5f5f5) { + // Set the text color + > label, + .help-block, + .help-inline { + color: @textColor; + } + // Style inputs accordingly + input, + textarea { + color: @textColor; + border-color: @borderColor; + &:focus { + border-color: darken(@borderColor, 10%); + .box-shadow(0 0 6px lighten(@borderColor, 20%)); + } + } + // Give a small background color for input-prepend/-append + .input-prepend .add-on, + .input-append .add-on { + color: @textColor; + background-color: @backgroundColor; + border-color: @textColor; + } +} +// Error +form .clearfix.error { + .formFieldState(#b94a48, #ee5f5b, lighten(#ee5f5b, 30%)); +} +// Warning +form .clearfix.warning { + .formFieldState(#c09853, #ccae64, lighten(#CCAE64, 5%)); +} +// Success +form .clearfix.success { + .formFieldState(#468847, #57a957, lighten(#57a957, 30%)); +} + + +// Form element sizes +// TODO v2: remove duplication here and just stick to .input-[size] in light of adding .spanN sizes +.input-mini, +input.mini, +textarea.mini, +select.mini { + width: 60px; +} +.input-small, +input.small, +textarea.small, +select.small { + width: 90px; +} +.input-medium, +input.medium, +textarea.medium, +select.medium { + width: 150px; +} +.input-large, +input.large, +textarea.large, +select.large { + width: 210px; +} +.input-xlarge, +input.xlarge, +textarea.xlarge, +select.xlarge { + width: 270px; +} +.input-xxlarge, +input.xxlarge, +textarea.xxlarge, +select.xxlarge { + width: 530px; +} +textarea.xxlarge { + overflow-y: auto; +} + +// Grid style input sizes +// This is a duplication of the main grid .columns() mixin, but subtracts 10px to account for input padding and border +.formColumns(@columnSpan: 1) { + display: inline-block; + float: none; + width: ((@gridColumnWidth) * @columnSpan) + (@gridGutterWidth * (@columnSpan - 1)) - 10; + margin-left: 0; +} +input, +textarea { + // Default columns + &.span1 { .formColumns(1); } + &.span2 { .formColumns(2); } + &.span3 { .formColumns(3); } + &.span4 { .formColumns(4); } + &.span5 { .formColumns(5); } + &.span6 { .formColumns(6); } + &.span7 { .formColumns(7); } + &.span8 { .formColumns(8); } + &.span9 { .formColumns(9); } + &.span10 { .formColumns(10); } + &.span11 { .formColumns(11); } + &.span12 { .formColumns(12); } + &.span13 { .formColumns(13); } + &.span14 { .formColumns(14); } + &.span15 { .formColumns(15); } + &.span16 { .formColumns(16); } +} + +// Disabled and read-only inputs +input[disabled], +select[disabled], +textarea[disabled], +input[readonly], +select[readonly], +textarea[readonly] { + background-color: #f5f5f5; + border-color: #ddd; + cursor: not-allowed; +} + +// Actions (the buttons) +.actions { + background: #f5f5f5; + margin-top: @baseline; + margin-bottom: @baseline; + padding: (@baseline - 1) 20px @baseline 150px; + border-top: 1px solid #ddd; + .border-radius(0 0 3px 3px); + .secondary-action { + float: right; + a { + line-height: 30px; + &:hover { + text-decoration: underline; + } + } + } +} + +// Help Text +// TODO: Do we need to set basefont and baseline here? +.help-inline, +.help-block { + font-size: @basefont; + line-height: @baseline; + color: @grayLight; +} +.help-inline { + padding-left: 5px; + *position: relative; /* IE6-7 */ + *top: -5px; /* IE6-7 */ +} + +// Big blocks of help text +.help-block { + display: block; + max-width: 600px; +} + +// Inline Fields (input fields that appear as inline objects +.inline-inputs { + color: @gray; + span { + padding: 0 2px 0 1px; + } +} + +// Allow us to put symbols and text within the input field for a cleaner look +.input-prepend, +.input-append { + input { + .border-radius(0 3px 3px 0); + } + .add-on { + position: relative; + background: #f5f5f5; + border: 1px solid #ccc; + z-index: 2; + float: left; + display: block; + width: auto; + min-width: 16px; + height: 18px; + padding: 4px 4px 4px 5px; + margin-right: -1px; + font-weight: normal; + line-height: 18px; + color: @grayLight; + text-align: center; + text-shadow: 0 1px 0 @white; + .border-radius(3px 0 0 3px); + } + .active { + background: lighten(@green, 30); + border-color: @green; + } +} +.input-prepend { + .add-on { + *margin-top: 1px; /* IE6-7 */ + } +} +.input-append { + input { + float: left; + .border-radius(3px 0 0 3px); + } + .add-on { + .border-radius(0 3px 3px 0); + margin-right: 0; + margin-left: -1px; + } +} + +// Stacked options for forms (radio buttons or checkboxes) +.inputs-list { + margin: 0 0 5px; + width: 100%; + li { + display: block; + padding: 0; + width: 100%; + } + label { + display: block; + float: none; + width: auto; + padding: 0; + margin-left: 20px; + line-height: @baseline; + text-align: left; + white-space: normal; + strong { + color: @gray; + } + small { + font-size: @basefont - 2; + font-weight: normal; + } + } + .inputs-list { + margin-left: 25px; + margin-bottom: 10px; + padding-top: 0; + } + &:first-child { + padding-top: 6px; + } + li + li { + padding-top: 2px; + } + input[type=radio], + input[type=checkbox] { + margin-bottom: 0; + margin-left: -20px; + float: left; + } +} + +// Stacked forms +.form-stacked { + padding-left: 20px; + fieldset { + padding-top: @baseline / 2; + } + legend { + padding-left: 0; + } + label { + display: block; + float: none; + width: auto; + font-weight: bold; + text-align: left; + line-height: 20px; + padding-top: 0; + } + .clearfix { + margin-bottom: @baseline / 2; + div.input { + margin-left: 0; + } + } + .inputs-list { + margin-bottom: 0; + li { + padding-top: 0; + label { + font-weight: normal; + padding-top: 0; + } + } + } + div.clearfix.error { + padding-top: 10px; + padding-bottom: 10px; + padding-left: 10px; + margin-top: 0; + margin-left: -10px; + } + .actions { + margin-left: -20px; + padding-left: 20px; + } +} +/* Patterns.less + * Repeatable UI elements outside the base styles provided from the scaffolding + * */ + + +// TOPBAR + +// Topbar for Branding and Nav +.topbar { + height: 40px; + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 10000; + overflow: visible; + + // Links get text shadow + a { + color: @grayLight; + text-shadow: 0 -1px 0 rgba(0,0,0,.25); + } + + // Hover and active states + // h3 for backwards compatibility + h3 a:hover, + .brand:hover, + ul .active > a { + background-color: #333; + background-color: rgba(255,255,255,.05); + color: @white; + text-decoration: none; + } + + // Website name + // h3 left for backwards compatibility + h3 { + position: relative; + } + h3 a, + .brand { + float: left; + display: block; + padding: 8px 20px 12px; + margin-left: -20px; // negative indent to left-align the text down the page + color: @white; + font-size: 20px; + font-weight: 200; + line-height: 1; + } + + // Plain text in topbar + p { + margin: 0; + line-height: 40px; + a:hover { + background-color: transparent; + color: @white; + } + } + + // Search Form + form { + float: left; + margin: 5px 0 0 0; + position: relative; + .opacity(100); + } + // Todo: remove from v2.0 when ready, added for legacy + form.pull-right { + float: right; + } + input { + background-color: #444; + background-color: rgba(255,255,255,.3); + #font > .sans-serif(13px, normal, 1); + padding: 4px 9px; + color: @white; + color: rgba(255,255,255,.75); + border: 1px solid #111; + .border-radius(4px); + @shadow: inset 0 1px 2px rgba(0,0,0,.1), 0 1px 0px rgba(255,255,255,.25); + .box-shadow(@shadow); + .transition(none); + + // Placeholder text gets special styles; can't be bundled together though for some reason + &:-moz-placeholder { + color: @grayLighter; + } + &::-webkit-input-placeholder { + color: @grayLighter; + } + // Hover states + &:hover { + background-color: @grayLight; + background-color: rgba(255,255,255,.5); + color: @white; + } + // Focus states (we use .focused since IE8 and down doesn't support :focus) + &:focus, + &.focused { + outline: 0; + background-color: @white; + color: @grayDark; + text-shadow: 0 1px 0 @white; + border: 0; + padding: 5px 10px; + .box-shadow(0 0 3px rgba(0,0,0,.15)); + } + } +} + +// gradient is applied to it's own element because overflow visible is not honored by ie when filter is present +// For backwards compatibility, include .topbar .fill +.topbar-inner, +.topbar .fill { + background-color: #222; + #gradient > .vertical(#333, #222); + @shadow: 0 1px 3px rgba(0,0,0,.25), inset 0 -1px 0 rgba(0,0,0,.1); + .box-shadow(@shadow); +} + + +// NAVIGATION + +// Topbar Nav +// ul.nav for all topbar based navigation to avoid inheritance issues and over-specificity +// For backwards compatibility, leave in .topbar div > ul +.topbar div > ul, +.nav { + display: block; + float: left; + margin: 0 10px 0 0; + position: relative; + left: 0; + > li { + display: block; + float: left; + } + a { + display: block; + float: none; + padding: 10px 10px 11px; + line-height: 19px; + text-decoration: none; + &:hover { + color: @white; + text-decoration: none; + } + } + .active > a { + background-color: #222; + background-color: rgba(0,0,0,.5); + } + + // Secondary (floated right) nav in topbar + &.secondary-nav { + float: right; + margin-left: 10px; + margin-right: 0; + // backwards compatibility + .menu-dropdown, + .dropdown-menu { + right: 0; + border: 0; + } + } + // Dropdowns within the .nav + // a.menu:hover and li.open .menu for backwards compatibility + a.menu:hover, + li.open .menu, + .dropdown-toggle:hover, + .dropdown.open .dropdown-toggle { + background: #444; + background: rgba(255,255,255,.05); + } + // .menu-dropdown for backwards compatibility + .menu-dropdown, + .dropdown-menu { + background-color: #333; + // a.menu for backwards compatibility + a.menu, + .dropdown-toggle { + color: @white; + &.open { + background: #444; + background: rgba(255,255,255,.05); + } + } + li a { + color: #999; + text-shadow: 0 1px 0 rgba(0,0,0,.5); + &:hover { + #gradient > .vertical(#292929,#191919); + color: @white; + } + } + .active a { + color: @white; + } + .divider { + background-color: #222; + border-color: #444; + } + } +} + +// For backwards compatibility with new dropdowns, redeclare dropdown link padding +.topbar ul .menu-dropdown li a, +.topbar ul .dropdown-menu li a { + padding: 4px 15px; +} + +// Dropdown Menus +// Use the .menu class on any
  • element within the topbar or ul.tabs and you'll get some superfancy dropdowns +// li.menu for backwards compatibility +li.menu, +.dropdown { + position: relative; +} +// The link that is clicked to toggle the dropdown +// a.menu for backwards compatibility +a.menu:after, +.dropdown-toggle:after { + width: 0; + height: 0; + display: inline-block; + content: "↓"; + text-indent: -99999px; + vertical-align: top; + margin-top: 8px; + margin-left: 4px; + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-top: 4px solid @white; + .opacity(50); +} +// The dropdown menu (ul) +// .menu-dropdown for backwards compatibility +.menu-dropdown, +.dropdown-menu { + background-color: @white; + float: left; + display: none; // None by default, but block on "open" of the menu + position: absolute; + top: 40px; + z-index: 900; + min-width: 160px; + max-width: 220px; + _width: 160px; + margin-left: 0; // override default ul styles + margin-right: 0; + padding: 6px 0; + zoom: 1; // do we need this? + border-color: #999; + border-color: rgba(0,0,0,.2); + border-style: solid; + border-width: 0 1px 1px; + .border-radius(0 0 6px 6px); + .box-shadow(0 2px 4px rgba(0,0,0,.2)); + .background-clip(padding-box); + + // Unfloat any li's to make them stack + li { + float: none; + display: block; + background-color: none; + } + // Dividers (basically an hr) within the dropdown + .divider { + height: 1px; + margin: 5px 0; + overflow: hidden; + background-color: #eee; + border-bottom: 1px solid @white; + } +} + +.topbar .dropdown-menu, +.dropdown-menu { + // Links within the dropdown menu + a { + display: block; + padding: 4px 15px; + clear: both; + font-weight: normal; + line-height: 18px; + color: @gray; + text-shadow: 0 1px 0 @white; + // Hover state + &:hover, + &.hover { + #gradient > .vertical(#eeeeee, #dddddd); + color: @grayDark; + text-decoration: none; + @shadow: inset 0 1px 0 rgba(0,0,0,.025), inset 0 -1px rgba(0,0,0,.025); + .box-shadow(@shadow); + } + } +} + +// Open state for the dropdown +// .open for backwards compatibility +.open, +.dropdown.open { + // .menu for backwards compatibility + .menu, + .dropdown-toggle { + color: @white; + background: #ccc; + background: rgba(0,0,0,.3); + } + // .menu-dropdown for backwards compatibility + .menu-dropdown, + .dropdown-menu { + display: block; + } +} + + +// TABS AND PILLS + +// Common styles +.tabs, +.pills { + margin: 0 0 @baseline; + padding: 0; + list-style: none; + .clearfix(); + > li { + float: left; + > a { + display: block; + } + } +} + +// Tabs +.tabs { + border-color: #ddd; + border-style: solid; + border-width: 0 0 1px; + > li { + position: relative; // For the dropdowns mostly + margin-bottom: -1px; + > a { + padding: 0 15px; + margin-right: 2px; + line-height: (@baseline * 2) - 2; + border: 1px solid transparent; + .border-radius(4px 4px 0 0); + &:hover { + text-decoration: none; + background-color: #eee; + border-color: #eee #eee #ddd; + } + } + } + // Active state, and it's :hover to override normal :hover + .active > a, + .active > a:hover { + color: @gray; + background-color: @white; + border: 1px solid #ddd; + border-bottom-color: transparent; + cursor: default; + } +} + +// Dropdowns in tabs +.tabs { + // first one for backwards compatibility + .menu-dropdown, + .dropdown-menu { + top: 35px; + border-width: 1px; + .border-radius(0 6px 6px 6px); + } + // first one for backwards compatibility + a.menu:after, + .dropdown-toggle:after { + border-top-color: #999; + margin-top: 15px; + margin-left: 5px; + } + // first one for backwards compatibility + li.open.menu .menu, + .open.dropdown .dropdown-toggle { + border-color: #999; + } + // first one for backwards compatibility + li.open a.menu:after, + .dropdown.open .dropdown-toggle:after { + border-top-color: #555; + } +} + +// Pills +.pills { + a { + margin: 5px 3px 5px 0; + padding: 0 15px; + line-height: 30px; + text-shadow: 0 1px 1px @white; + .border-radius(15px); + &:hover { + color: @white; + text-decoration: none; + text-shadow: 0 1px 1px rgba(0,0,0,.25); + background-color: @linkColorHover; + } + } + .active a { + color: @white; + text-shadow: 0 1px 1px rgba(0,0,0,.25); + background-color: @linkColor; + } +} + +// Stacked pills +.pills-vertical > li { + float: none; +} + +// Tabbable areas +.tab-content, +.pill-content { +} +.tab-content > .tab-pane, +.pill-content > .pill-pane, +.tab-content > div, +.pill-content > div { + display: none; +} +.tab-content > .active, +.pill-content > .active { + display: block; +} + + +// BREADCRUMBS + +.breadcrumb { + padding: 7px 14px; + margin: 0 0 @baseline; + #gradient > .vertical(#ffffff, #f5f5f5); + border: 1px solid #ddd; + .border-radius(3px); + .box-shadow(inset 0 1px 0 @white); + li { + display: inline; + text-shadow: 0 1px 0 @white; + } + .divider { + padding: 0 5px; + color: @grayLight; + } + .active a { + color: @grayDark; + } +} + + +// PAGE HEADERS + +.hero-unit { + background-color: #f5f5f5; + margin-bottom: 30px; + padding: 60px; + .border-radius(6px); + h1 { + margin-bottom: 0; + font-size: 60px; + line-height: 1; + letter-spacing: -1px; + } + p { + font-size: 18px; + font-weight: 200; + line-height: @baseline * 1.5; + } +} +footer { + margin-top: @baseline - 1; + padding-top: @baseline - 1; + border-top: 1px solid #eee; +} + + +// PAGE HEADERS + +.page-header { + margin-bottom: @baseline - 1; + border-bottom: 1px solid #ddd; + .box-shadow(0 1px 0 rgba(255,255,255,.5)); + h1 { + margin-bottom: (@baseline / 2) - 1px; + } +} + + +// BUTTON STYLES + +// Shared colors for buttons and alerts +.btn, +.alert-message { + // Set text color + &.danger, + &.danger:hover, + &.error, + &.error:hover, + &.success, + &.success:hover, + &.info, + &.info:hover { + color: @white + } + // Sets the close button to the middle of message + .close{ + font-family: Arial, sans-serif; + line-height: 18px; + } + // Danger and error appear as red + &.danger, + &.error { + .gradientBar(#ee5f5b, #c43c35); + } + // Success appears as green + &.success { + .gradientBar(#62c462, #57a957); + } + // Info appears as a neutral blue + &.info { + .gradientBar(#5bc0de, #339bb9); + } +} + +// Base .btn styles +.btn { + // Button Base + cursor: pointer; + display: inline-block; + #gradient > .vertical-three-colors(#ffffff, #ffffff, 25%, darken(#ffffff, 10%)); // Don't use .gradientbar() here since it does a three-color gradient + padding: 5px 14px 6px; + text-shadow: 0 1px 1px rgba(255,255,255,.75); + color: #333; + font-size: @basefont; + line-height: normal; + border: 1px solid #ccc; + border-bottom-color: #bbb; + .border-radius(4px); + @shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05); + .box-shadow(@shadow); + + &:hover { + background-position: 0 -15px; + color: #333; + text-decoration: none; + } + + // Focus state for keyboard and accessibility + &:focus { + outline: 1px dotted #666; + } + + // Primary Button Type + &.primary { + color: @white; + .gradientBar(@blue, @blueDark) + } + + // Transitions + .transition(.1s linear all); + + // Active and Disabled states + &.active, + &:active { + @shadow: inset 0 2px 4px rgba(0,0,0,.25), 0 1px 2px rgba(0,0,0,.05); + .box-shadow(@shadow); + } + &.disabled { + cursor: default; + background-image: none; + .reset-filter(); + .opacity(65); + .box-shadow(none); + } + &[disabled] { + // disabled pseudo can't be included with .disabled + // def because IE8 and below will drop it ;_; + cursor: default; + background-image: none; + .reset-filter(); + .opacity(65); + .box-shadow(none); + } + + // Button Sizes + &.large { + font-size: @basefont + 2px; + line-height: normal; + padding: 9px 14px 9px; + .border-radius(6px); + } + &.small { + padding: 7px 9px 7px; + font-size: @basefont - 2px; + } +} +// Super jank hack for removing border-radius from IE9 so we can keep filter gradients on alerts and buttons +:root .alert-message, +:root .btn { + border-radius: 0 \0; +} + +// Help Firefox not be a jerk about adding extra padding to buttons +button.btn, +input[type=submit].btn { + &::-moz-focus-inner { + padding: 0; + border: 0; + } +} + + +// CLOSE ICONS +.close { + float: right; + color: @black; + font-size: 20px; + font-weight: bold; + line-height: @baseline * .75; + text-shadow: 0 1px 0 rgba(255,255,255,1); + .opacity(25); + &:hover { + color: @black; + text-decoration: none; + .opacity(40); + } +} + + +// ERROR STYLES + +// Base alert styles +.alert-message { + position: relative; + padding: 7px 15px; + margin-bottom: @baseline; + color: @grayDark; + .gradientBar(#fceec1, #eedc94); // warning by default + text-shadow: 0 1px 0 rgba(255,255,255,.5); + border-width: 1px; + border-style: solid; + .border-radius(4px); + .box-shadow(inset 0 1px 0 rgba(255,255,255,.25)); + + // Adjust close icon + .close { + margin-top: 1px; + *margin-top: 0; // For IE7 + } + + // Make links same color as text and stand out more + a { + font-weight: bold; + color: @grayDark; + } + &.danger p a, + &.error p a, + &.success p a, + &.info p a { + color: @white; + } + + // Remove extra margin from content + h5 { + line-height: @baseline; + } + p { + margin-bottom: 0; + } + div { + margin-top: 5px; + margin-bottom: 2px; + line-height: 28px; + } + .btn { + // Provide actions with buttons + .box-shadow(0 1px 0 rgba(255,255,255,.25)); + } + + &.block-message { + background-image: none; + background-color: lighten(#fceec1, 5%); + .reset-filter(); + padding: 14px; + border-color: #fceec1; + .box-shadow(none); + ul, p { + margin-right: 30px; + } + ul { + margin-bottom: 0; + } + li { + color: @grayDark; + } + .alert-actions { + margin-top: 5px; + } + &.error, + &.success, + &.info { + color: @grayDark; + text-shadow: 0 1px 0 rgba(255,255,255,.5); + } + &.error { + background-color: lighten(#f56a66, 25%); + border-color: lighten(#f56a66, 20%); + } + &.success { + background-color: lighten(#62c462, 30%); + border-color: lighten(#62c462, 25%); + } + &.info { + background-color: lighten(#6bd0ee, 25%); + border-color: lighten(#6bd0ee, 20%); + } + // Change link color back + &.danger p a, + &.error p a, + &.success p a, + &.info p a { + color: @grayDark; + } + + } +} + + +// PAGINATION + +.pagination { + height: @baseline * 2; + margin: @baseline 0; + ul { + float: left; + margin: 0; + border: 1px solid #ddd; + border: 1px solid rgba(0,0,0,.15); + .border-radius(3px); + .box-shadow(0 1px 2px rgba(0,0,0,.05)); + } + li { + display: inline; + } + a { + float: left; + padding: 0 14px; + line-height: (@baseline * 2) - 2; + border-right: 1px solid; + border-right-color: #ddd; + border-right-color: rgba(0,0,0,.15); + *border-right-color: #ddd; /* IE6-7 */ + text-decoration: none; + } + a:hover, + .active a { + background-color: lighten(@blue, 45%); + } + .disabled a, + .disabled a:hover { + background-color: transparent; + color: @grayLight; + } + .next a { + border: 0; + } +} + + +// WELLS + +.well { + background-color: #f5f5f5; + margin-bottom: 20px; + padding: 19px; + min-height: 20px; + border: 1px solid #eee; + border: 1px solid rgba(0,0,0,.05); + .border-radius(4px); + .box-shadow(inset 0 1px 1px rgba(0,0,0,.05)); + blockquote { + border-color: #ddd; + border-color: rgba(0,0,0,.15); + } +} + + +// MODALS + +.modal-backdrop { + background-color: @black; + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 10000; + // Fade for backdrop + &.fade { opacity: 0; } +} + +.modal-backdrop, +.modal-backdrop.fade.in { + .opacity(80); +} + +.modal { + position: fixed; + top: 50%; + left: 50%; + z-index: 11000; + max-height: 500px; + overflow: auto; + width: 560px; + margin: -250px 0 0 -280px; + background-color: @white; + border: 1px solid #999; + border: 1px solid rgba(0,0,0,.3); + *border: 1px solid #999; /* IE6-7 */ + .border-radius(6px); + .box-shadow(0 3px 7px rgba(0,0,0,0.3)); + .background-clip(padding-box); + .close { margin-top: 7px; } + &.fade { + .transition(e('opacity .3s linear, top .3s ease-out')); + top: -25%; + } + &.fade.in { top: 50%; } +} +.modal-header { + border-bottom: 1px solid #eee; + padding: 5px 15px; +} +.modal-body { + padding: 15px; +} +.modal-body form { + margin-bottom: 0; +} +.modal-footer { + background-color: #f5f5f5; + padding: 14px 15px 15px; + border-top: 1px solid #ddd; + .border-radius(0 0 6px 6px); + .box-shadow(inset 0 1px 0 @white); + .clearfix(); + margin-bottom: 0; + .btn { + float: right; + margin-left: 5px; + } +} + +// Fix the stacking of these components when in modals +.modal .popover, +.modal .twipsy { + z-index: 12000; +} + + +// POPOVER ARROWS + +#popoverArrow { + .above(@arrowWidth: 5px) { + bottom: 0; + left: 50%; + margin-left: -@arrowWidth; + border-left: @arrowWidth solid transparent; + border-right: @arrowWidth solid transparent; + border-top: @arrowWidth solid @black; + } + .left(@arrowWidth: 5px) { + top: 50%; + right: 0; + margin-top: -@arrowWidth; + border-top: @arrowWidth solid transparent; + border-bottom: @arrowWidth solid transparent; + border-left: @arrowWidth solid @black; + } + .below(@arrowWidth: 5px) { + top: 0; + left: 50%; + margin-left: -@arrowWidth; + border-left: @arrowWidth solid transparent; + border-right: @arrowWidth solid transparent; + border-bottom: @arrowWidth solid @black; + } + .right(@arrowWidth: 5px) { + top: 50%; + left: 0; + margin-top: -@arrowWidth; + border-top: @arrowWidth solid transparent; + border-bottom: @arrowWidth solid transparent; + border-right: @arrowWidth solid @black; + } +} + +// TWIPSY + +.twipsy { + display: block; + position: absolute; + visibility: visible; + padding: 5px; + font-size: 11px; + z-index: 1000; + .opacity(80); + &.fade.in { + .opacity(80); + } + &.above .twipsy-arrow { #popoverArrow > .above(); } + &.left .twipsy-arrow { #popoverArrow > .left(); } + &.below .twipsy-arrow { #popoverArrow > .below(); } + &.right .twipsy-arrow { #popoverArrow > .right(); } +} +.twipsy-inner { + padding: 3px 8px; + background-color: @black; + color: white; + text-align: center; + max-width: 200px; + text-decoration: none; + .border-radius(4px); +} +.twipsy-arrow { + position: absolute; + width: 0; + height: 0; +} + + +// POPOVERS + +.popover { + position: absolute; + top: 0; + left: 0; + z-index: 1000; + padding: 5px; + display: none; + &.above .arrow { #popoverArrow > .above(); } + &.right .arrow { #popoverArrow > .right(); } + &.below .arrow { #popoverArrow > .below(); } + &.left .arrow { #popoverArrow > .left(); } + .arrow { + position: absolute; + width: 0; + height: 0; + } + .inner { + background: @black; + background: rgba(0,0,0,.8); + padding: 3px; + overflow: hidden; + width: 280px; + .border-radius(6px); + .box-shadow(0 3px 7px rgba(0,0,0,0.3)); + } + .title { + background-color: #f5f5f5; + padding: 9px 15px; + line-height: 1; + .border-radius(3px 3px 0 0); + border-bottom:1px solid #eee; + } + .content { + background-color: @white; + padding: 14px; + .border-radius(0 0 3px 3px); + .background-clip(padding-box); + p, ul, ol { + margin-bottom: 0; + } + } +} + + +// PATTERN ANIMATIONS + +.fade { + .transition(opacity .15s linear); + opacity: 0; + &.in { + opacity: 1; + } +} + + +// LABELS + +.label { + padding: 1px 3px 2px; + font-size: @basefont * .75; + font-weight: bold; + color: @white; + text-transform: uppercase; + white-space: nowrap; + background-color: @grayLight; + .border-radius(3px); + text-shadow: none; + &.important { background-color: #c43c35; } + &.warning { background-color: @orange; } + &.success { background-color: @green; } + &.notice { background-color: lighten(@blue, 25%); } +} + + +// MEDIA GRIDS + +.media-grid { + margin-left: -@gridGutterWidth; + margin-bottom: 0; + .clearfix(); + li { + display: inline; + } + a { + float: left; + padding: 4px; + margin: 0 0 @baseline @gridGutterWidth; + border: 1px solid #ddd; + .border-radius(4px); + .box-shadow(0 1px 1px rgba(0,0,0,.075)); + img { + display: block; + } + &:hover { + border-color: @linkColor; + .box-shadow(0 1px 4px rgba(0,105,214,.25)); + } + } +} diff --git a/skeleton/styles/default.less b/skeleton/styles/default.less index e69de29..cee8595 100644 --- a/skeleton/styles/default.less +++ b/skeleton/styles/default.less @@ -0,0 +1,49 @@ +/* Override some defaults */ +html, body { + background-color: #eee; +} +body { + padding-top: 40px; /* 40px to make the container go all the way to the bottom of the topbar */ +} +.container > footer p { + text-align: center; /* center align it with the container */ +} +.container { + width: 820px; /* downsize our container to make the content feel a bit tighter and more cohesive. NOTE: this removes two full columns from the grid, meaning you only go to 14 columns and not 16. */ +} + +/* The white background content wrapper */ +.container .content { + background-color: #fff; + padding: 20px; + margin: 0 -20px; /* negative indent the amount of the padding to maintain the grid system */ + -webkit-border-radius: 0 0 6px 6px; + -moz-border-radius: 0 0 6px 6px; + border-radius: 0 0 6px 6px; + -webkit-box-shadow: 0 1px 2px rgba(0,0,0,.15); + -moz-box-shadow: 0 1px 2px rgba(0,0,0,.15); + box-shadow: 0 1px 2px rgba(0,0,0,.15); +} + +/* Page header tweaks */ +.page-header { + background-color: #f5f5f5; + padding: 20px 20px 10px; + margin: -20px -20px 20px; +} + +/* Styles you shouldn't keep as they are for displaying this base example only */ +.content .span10, +.content .span4 { + min-height: 500px; +} +/* Give a quick and non-cross-browser friendly divider */ +.content .span4 { + margin-left: 5px; + padding-left: 14px; + border-left: 1px solid #eee; +} + +.topbar .btn { + border: 0; +} diff --git a/tests/Phrozn/Bundle/ServiceTest.php b/tests/Phrozn/Bundle/ServiceTest.php index b1d58a1..8c791ba 100644 --- a/tests/Phrozn/Bundle/ServiceTest.php +++ b/tests/Phrozn/Bundle/ServiceTest.php @@ -1,23 +1,20 @@ service->getBundles(Bundle::TYPE_ALL, 'processor.test'); $this->assertArrayHasKey('processor.test', $bundles); $this->assertFalse(isset($bundles['processor.hatena'])); - + // test search exact (by name) $bundles = $this->service->getBundles(Bundle::TYPE_ALL, 'HatenaSyntax'); $this->assertArrayHasKey('processor.hatena', $bundles); $this->assertFalse(isset($bundles['processor.test'])); - + // test search several items $bundles = $this->service->getBundles(Bundle::TYPE_ALL, 'processor'); // list all processors $this->assertArrayHasKey('processor.test', $bundles); @@ -95,7 +92,7 @@ public function testListInstalled() { $bundles = $this->service->getBundles(Bundle::TYPE_INSTALLED); $this->assertSame(array(), $bundles); - + $path = dirname(__FILE__) . '/project/'; $bundle = 'test'; $this->assertFalse(file_exists($path . '.phrozn/plugins/Processor/Test.php')); @@ -118,7 +115,7 @@ public function testListInstalledSearch() { $bundles = $this->service->getBundles(Bundle::TYPE_INSTALLED); $this->assertSame(array(), $bundles); - + $path = dirname(__FILE__) . '/project/'; $bundle = 'test'; $this->assertFalse(file_exists($path . '.phrozn/plugins/Processor/Test.php')); @@ -289,7 +286,7 @@ public function testApplyOfficialBundleByName() public function testAlreadyInstalledException() { - $this->setExpectedException('Exception', + $this->setExpectedException('Exception', 'Bundle "processor.test" is already installed.'); $path = dirname(__FILE__) . '/project/'; $bundle = 'test'; diff --git a/tests/Phrozn/BundleTest.php b/tests/Phrozn/BundleTest.php index 20af787..8699e4c 100644 --- a/tests/Phrozn/BundleTest.php +++ b/tests/Phrozn/BundleTest.php @@ -1,23 +1,20 @@ assertSame($basePath . '.phrozn', $path->get()); $this->assertSame( - $basePath . '.phrozn', + $basePath . '.phrozn', $path->set($basePath . 'sub')->get()); $this->assertSame( $basePath . '.phrozn', diff --git a/tests/Phrozn/Processor/LessTest.php b/tests/Phrozn/Processor/LessTest.php index d0c00f5..097b7a1 100644 --- a/tests/Phrozn/Processor/LessTest.php +++ b/tests/Phrozn/Processor/LessTest.php @@ -1,23 +1,20 @@ save(); $this->assertTrue(file_exists($path . '/.phrozn/.registry')); $this->assertSame( - file_get_contents(dirname(__FILE__) . '/../project/registry.serialized'), + file_get_contents(dirname(__FILE__) . '/../project/registry.serialized'), file_get_contents($path . '/.phrozn/.registry')); // test read @@ -81,7 +78,7 @@ public function testSaveRead() $container->read(); $this->assertSame('test.me', $container->get('bundle')); $this->assertSame(array(1, 2, 3), $container->get('template')); - + @unlink($path . '/.phrozn/.registry'); } @@ -101,7 +98,7 @@ public function testNoRegistryFile() public function testNoPathException() { $this->setExpectedException('Exception', 'No project path provided'); - + $container = new Container(); $dao = new Dao($container); $dao->save(); diff --git a/tests/Phrozn/Runner/CommandLine/Callback/BundleTest.php b/tests/Phrozn/Runner/CommandLine/Callback/BundleTest.php index 60c1c5a..d4d30cd 100644 --- a/tests/Phrozn/Runner/CommandLine/Callback/BundleTest.php +++ b/tests/Phrozn/Runner/CommandLine/Callback/BundleTest.php @@ -1,23 +1,20 @@ resetProjectDirectory(); - + $this->outputter = new Outputter($this); $runner = new Callback(); $data['paths'] = $paths; // inject paths @@ -103,7 +100,7 @@ public function testBundleListWrongProjectPath() $out = $this->outputter; $path = '/wrong-path'; - + $result = $this->getParseResult("phr-dev bundle list test {$path}"); $this->runner ->setUnitTestData('no') @@ -134,7 +131,7 @@ public function testBundleInfoByQue() ->execute(); $out->assertInLogs('Test processor plugin - used to demonstrate how'); } - + public function testBundleInfoNotFound() { $out = $this->outputter; @@ -170,7 +167,7 @@ public function testBundleApplyByIdWithYesWithImplicitPath() $out = $this->outputter; $path = dirname(__FILE__) . '/project'; - + $this->assertFalse(file_exists($path . '/.phrozn/plugins/Processor/Test.php')); $this->assertFalse(file_exists($path . '/.phrozn/plugins/Site/View/Test.php')); @@ -255,7 +252,7 @@ public function testBundleApplyByIdWithNoWithExplicitPath() $out = $this->outputter; $path = dirname(__FILE__) . '/project'; - + $result = $this->getParseResult("phr-dev bundle apply test {$path}"); $this->runner ->setUnitTestData('no') @@ -274,7 +271,7 @@ public function testBundleApplyByIdWithYesWithExplicitPath() $out = $this->outputter; $path = dirname(__FILE__) . '/project'; - + $this->assertFalse(file_exists($path . '/.phrozn/plugins/Processor/Test.php')); $this->assertFalse(file_exists($path . '/.phrozn/plugins/Site/View/Test.php')); @@ -299,7 +296,7 @@ public function testBundleApplyWrongProjectPath() $out = $this->outputter; $path = '/wrong-path'; - + $result = $this->getParseResult("phr-dev bundle apply test {$path}"); $this->runner ->setUnitTestData('no') @@ -314,7 +311,7 @@ public function testBundleApplyEmptyBundle() $out = $this->outputter; $path = dirname(__FILE__) . '/project'; - + $result = $this->getParseResult("phr-dev bundle apply empty.bundle {$path}"); $this->runner ->setUnitTestData('yes') @@ -328,7 +325,7 @@ public function testBundleClobberNonInstalledBundle() $out = $this->outputter; $path = dirname(__FILE__) . '/project'; - + $this->assertFalse(file_exists($path . '/.phrozn/plugins/Processor/Test.php')); $this->assertFalse(file_exists($path . '/.phrozn/plugins/Site/View/Test.php')); @@ -345,7 +342,7 @@ public function testBundleClobberByIdWithYesWithExplicitPath() $out = $this->outputter; $path = dirname(__FILE__) . '/project'; - + $this->assertFalse(file_exists($path . '/.phrozn/plugins/Processor/Test.php')); $this->assertFalse(file_exists($path . '/.phrozn/plugins/Site/View/Test.php')); @@ -386,7 +383,7 @@ public function testBundleClobberByIdWithNoWithExplicitPath() $out = $this->outputter; $path = dirname(__FILE__) . '/project'; - + $this->assertFalse(file_exists($path . '/.phrozn/plugins/Processor/Test.php')); $this->assertFalse(file_exists($path . '/.phrozn/plugins/Site/View/Test.php')); @@ -427,7 +424,7 @@ public function testBundleClobberWrongProjectPath() $out = $this->outputter; $path = '/wrong-path'; - + $result = $this->getParseResult("phr-dev bundle clobber test {$path}"); $this->runner ->setUnitTestData('no') @@ -436,7 +433,7 @@ public function testBundleClobberWrongProjectPath() $out->assertInLogs('[FAIL] No project found at /wrong-path'); } - + public function testNoSubActionSpecified() { $out = $this->outputter; diff --git a/tests/Phrozn/Runner/CommandLine/Callback/ClobberTest.php b/tests/Phrozn/Runner/CommandLine/Callback/ClobberTest.php index 6d65f85..ec6910a 100644 --- a/tests/Phrozn/Runner/CommandLine/Callback/ClobberTest.php +++ b/tests/Phrozn/Runner/CommandLine/Callback/ClobberTest.php @@ -1,23 +1,20 @@ removeProjectDirectory(); - + $this->outputter = new Outputter($this); $runner = new Callback(); $data['paths'] = $paths; // inject paths diff --git a/tests/Phrozn/Runner/CommandLine/Callback/HelpTest.php b/tests/Phrozn/Runner/CommandLine/Callback/HelpTest.php index 4278aec..931c80c 100644 --- a/tests/Phrozn/Runner/CommandLine/Callback/HelpTest.php +++ b/tests/Phrozn/Runner/CommandLine/Callback/HelpTest.php @@ -1,23 +1,20 @@ removeProjectDirectory(); - + $this->outputter = new Outputter($this); $runner = new Callback(); $data['paths'] = $paths; // inject paths diff --git a/tests/Phrozn/Runner/CommandLine/Callback/UpTest.php b/tests/Phrozn/Runner/CommandLine/Callback/UpTest.php index e4e91bb..5ffd1e7 100644 --- a/tests/Phrozn/Runner/CommandLine/Callback/UpTest.php +++ b/tests/Phrozn/Runner/CommandLine/Callback/UpTest.php @@ -1,23 +1,20 @@ removeProjectDirectory(); - + $this->outputter = new Outputter($this); $runner = new Callback(); $data['paths'] = $paths; // inject paths diff --git a/tests/Phrozn/Runner/CommandLine/CommandTest.php b/tests/Phrozn/Runner/CommandLine/CommandTest.php index a9450ba..219c1ad 100644 --- a/tests/Phrozn/Runner/CommandLine/CommandTest.php +++ b/tests/Phrozn/Runner/CommandLine/CommandTest.php @@ -1,23 +1,20 @@ assertTrue(isset($parser->commands)); } - + public function testSubcommands() { $this->subcommand('initialize'); diff --git a/tests/Phrozn/Runner/CommandLine/ReaderTest.php b/tests/Phrozn/Runner/CommandLine/ReaderTest.php index 2310eaf..ad914b7 100644 --- a/tests/Phrozn/Runner/CommandLine/ReaderTest.php +++ b/tests/Phrozn/Runner/CommandLine/ReaderTest.php @@ -1,23 +1,20 @@ assertSame($original, $rendered); } @@ -75,7 +72,7 @@ public function testRunHUpdate() '-h', )); $path = dirname(__FILE__) . '/output/phr-help.out'; - $original = file_get_contents($path); + $original = file_get_contents($path); $rendered = implode("", array_slice(file(self::STDOUT), 1)); $this->assertSame($original, $rendered); } diff --git a/tests/Phrozn/Site/DefaultSiteTest.php b/tests/Phrozn/Site/DefaultSiteTest.php index f37a8fb..52b823a 100644 --- a/tests/Phrozn/Site/DefaultSiteTest.php +++ b/tests/Phrozn/Site/DefaultSiteTest.php @@ -1,23 +1,20 @@ cleanOutputDirectory(); - + } public function tearDown() @@ -71,7 +68,7 @@ public function testSiteCompilation() $rendered = file_get_contents($path . 'site/textile.html'); $this->assertSame($static, $rendered); - $outputter->assertInLogs(str_replace(dirname(__FILE__), '', $path) . 'entries/skipped.twig SKIPPED'); + $outputter->assertInLogs(str_replace(dirname(__FILE__), '', $path) . 'entries/skipped.twig SKIPPED'); } public function testSiteCompilationEntriesNotFound() diff --git a/tests/Phrozn/Site/View/BaseTest.php b/tests/Phrozn/Site/View/BaseTest.php index a14ce26..0c25919 100644 --- a/tests/Phrozn/Site/View/BaseTest.php +++ b/tests/Phrozn/Site/View/BaseTest.php @@ -1,23 +1,20 @@ assertSame('style.css', basename($view->getInputFile())); diff --git a/tests/Phrozn/Site/View/FactoryTest.php b/tests/Phrozn/Site/View/FactoryTest.php index 2e329a9..4eea060 100644 --- a/tests/Phrozn/Site/View/FactoryTest.php +++ b/tests/Phrozn/Site/View/FactoryTest.php @@ -1,23 +1,20 @@ assertSame('test.js', basename($view->getInputFile())); diff --git a/tests/Phrozn/Site/View/LessTest.php b/tests/Phrozn/Site/View/LessTest.php index 9019ccf..50cc824 100644 --- a/tests/Phrozn/Site/View/LessTest.php +++ b/tests/Phrozn/Site/View/LessTest.php @@ -1,23 +1,20 @@ assertSame('style.less', basename($view->getInputFile())); diff --git a/tests/Phrozn/Site/View/MarkdownTest.php b/tests/Phrozn/Site/View/MarkdownTest.php index 9eaf7db..e842b68 100644 --- a/tests/Phrozn/Site/View/MarkdownTest.php +++ b/tests/Phrozn/Site/View/MarkdownTest.php @@ -1,23 +1,20 @@ assertSame('markdown.markdown', basename($view->getInputFile())); diff --git a/tests/Phrozn/Site/View/OutputPathTest.php b/tests/Phrozn/Site/View/OutputPathTest.php index f41538e..6bf0274 100644 --- a/tests/Phrozn/Site/View/OutputPathTest.php +++ b/tests/Phrozn/Site/View/OutputPathTest.php @@ -1,23 +1,20 @@ assertSame('htaccess', basename($view->getInputFile())); diff --git a/tests/Phrozn/Site/View/TextileTest.php b/tests/Phrozn/Site/View/TextileTest.php index e35a162..c4c5aa2 100644 --- a/tests/Phrozn/Site/View/TextileTest.php +++ b/tests/Phrozn/Site/View/TextileTest.php @@ -1,23 +1,20 @@ assertSame('textile.textile', basename($view->getInputFile())); diff --git a/tests/Phrozn/Site/View/TwigTest.php b/tests/Phrozn/Site/View/TwigTest.php index c2f22d0..beacca2 100644 --- a/tests/Phrozn/Site/View/TwigTest.php +++ b/tests/Phrozn/Site/View/TwigTest.php @@ -1,23 +1,20 @@ assertSame('2011-02-24-compile.twig', basename($view->getInputFile())); @@ -105,7 +102,7 @@ public function testViewCompilingPermalinkSetParametrizedIndexAdded() { $twig = dirname(__FILE__) . '/../project/.phrozn/entries/compile-permalink-append-index.twig'; $html = dirname(__FILE__) . '/../project/.phrozn/entries/compile-permalink.html'; - $path = dirname(__FILE__) . '/out/'; + $path = dirname(__FILE__) . '/out/'; $view = new View($twig, $path); $this->assertSame('compile-permalink-append-index.twig', basename($view->getInputFile())); @@ -134,7 +131,7 @@ public function testViewCompilingPermalinkSetParametrized() { $twig = dirname(__FILE__) . '/../project/.phrozn/entries/compile-permalink-parametrized.twig'; $html = dirname(__FILE__) . '/../project/.phrozn/entries/compile-permalink.html'; - $path = dirname(__FILE__) . '/out/'; + $path = dirname(__FILE__) . '/out/'; $view = new View($twig, $path); $this->assertSame('compile-permalink-parametrized.twig', basename($view->getInputFile()));