├── .DS_Store ├── README.md ├── includes └── lessc.inc.php ├── index.php └── lib ├── bootstrap.less ├── forms.less ├── mixins.less ├── patterns.less ├── reset.less ├── scaffolding.less ├── tables.less ├── type.less └── variables.less /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinbean/bootstrap-generator/7616be3e49e834694de65606dff972d0ffe6320d/.DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | The Bootstrap Generator is for use in kick-starting your projects that use Twitter's Bootstrap CSS framework. Simply configure the options, click "Generate" and receive your own, compiled Bootstrap CSS file ready to work with! -------------------------------------------------------------------------------- /includes/lessc.inc.php: -------------------------------------------------------------------------------- 1 | 10 | * Licensed under MIT or GPLv3, see LICENSE 11 | */ 12 | 13 | 14 | /** 15 | * The less compiler and parser. 16 | * 17 | * Converting LESS to CSS is a two stage process. First the incoming document 18 | * must be parsed. Parsing creates a tree in memory that represents the 19 | * structure of the document. Then, the tree of the document is recursively 20 | * compiled into the CSS text. The compile step has an implicit step called 21 | * reduction, where values are brought to their lowest form before being 22 | * turned to text, eg. mathematical equations are solved, and variables are 23 | * dereferenced. 24 | * 25 | * The parsing stage produces the final structure of the document, for this 26 | * reason mixins are mixed in and attribute accessors are referenced during 27 | * the parse step. A reduction is done on the mixed in block as it is mixed in. 28 | * 29 | * See the following: 30 | * - entry point for parsing and compiling: lessc::parse() 31 | * - parsing: lessc::parseChunk() 32 | * - compiling: lessc::compileBlock() 33 | * 34 | */ 35 | class lessc { 36 | protected $buffer; 37 | protected $count; 38 | protected $line; 39 | protected $libFunctions = array(); 40 | static protected $nextBlockId = 0; 41 | 42 | public $indentLevel; 43 | public $indentChar = ' '; 44 | 45 | protected $env = null; 46 | 47 | protected $allParsedFiles = array(); 48 | 49 | public $vPrefix = '@'; // prefix of abstract properties 50 | public $mPrefix = '$'; // prefix of abstract blocks 51 | public $imPrefix = '!'; // special character to add !important 52 | public $parentSelector = '&'; 53 | 54 | static protected $precedence = array( 55 | '+' => 0, 56 | '-' => 0, 57 | '*' => 1, 58 | '/' => 1, 59 | '%' => 1, 60 | ); 61 | static protected $operatorString; // regex string to match any of the operators 62 | 63 | // types that have delayed computation 64 | static protected $dtypes = array('expression', 'variable', 65 | 'function', 'negative', 'list', 'lookup'); 66 | 67 | /** 68 | * @link http://www.w3.org/TR/css3-values/ 69 | */ 70 | static protected $units = array( 71 | 'em', 'ex', 'px', 'gd', 'rem', 'vw', 'vh', 'vm', 'ch', // Relative length units 72 | 'in', 'cm', 'mm', 'pt', 'pc', // Absolute length units 73 | '%', // Percentages 74 | 'deg', 'grad', 'rad', 'turn', // Angles 75 | 'ms', 's', // Times 76 | 'Hz', 'kHz', //Frequencies 77 | ); 78 | 79 | public $importDisabled = false; 80 | public $importDir = ''; 81 | 82 | public $compat = false; // lessjs compatibility mode, does nothing right now 83 | 84 | /** 85 | * if we are in an expression then we don't need to worry about parsing font shorthand 86 | * $inExp becomes true after the first value in an expression, or if we enter parens 87 | */ 88 | protected $inExp = false; 89 | 90 | /** 91 | * if we are in parens we can be more liberal with whitespace around operators because 92 | * it must evaluate to a single value and thus is less ambiguous. 93 | * 94 | * Consider: 95 | * property1: 10 -5; // is two numbers, 10 and -5 96 | * property2: (10 -5); // should evaluate to 5 97 | */ 98 | protected $inParens = false; 99 | 100 | /** 101 | * Parse a single chunk off the head of the buffer and place it. 102 | * @return false when the buffer is empty, or there is an error 103 | * 104 | * This functions is called repeatedly until the entire document is 105 | * parsed. 106 | * 107 | * This parser is most similar to a recursive descent parser. Single 108 | * functions represent discrete grammatical rules for the language, and 109 | * they are able to capture the text that represents those rules. 110 | * 111 | * Consider the function lessc::keyword(). (all parse functions are 112 | * structured the same) 113 | * 114 | * The function takes a single reference argument. When calling the the 115 | * function it will attempt to match a keyword on the head of the buffer. 116 | * If it is successful, it will place the keyword in the referenced 117 | * argument, advance the position in the buffer, and return true. If it 118 | * fails then it won't advance the buffer and it will return false. 119 | * 120 | * All of these parse functions are powered by lessc::match(), which behaves 121 | * the same way, but takes a literal regular expression. Sometimes it is 122 | * more convenient to use match instead of creating a new function. 123 | * 124 | * Because of the format of the functions, to parse an entire string of 125 | * grammatical rules, you can chain them together using &&. 126 | * 127 | * But, if some of the rules in the chain succeed before one fails, then 128 | * then buffer position will be left at an invalid state. In order to 129 | * avoid this, lessc::seek() is used to remember and set buffer positions. 130 | * 131 | * Before doing a chain, use $s = $this->seek() to remember the current 132 | * position into $s. Then if a chain fails, use $this->seek($s) to 133 | * go back where we started. 134 | */ 135 | function parseChunk() { 136 | if (empty($this->buffer)) return false; 137 | $s = $this->seek(); 138 | 139 | // setting a property 140 | if ($this->keyword($key) && $this->assign() && 141 | $this->propertyValue($value) && $this->end()) 142 | { 143 | $this->append(array('assign', $key, $value)); 144 | return true; 145 | } else { 146 | $this->seek($s); 147 | } 148 | 149 | // look for special css blocks 150 | if ($this->env->parent == null && $this->literal('@', false)) { 151 | $this->count--; 152 | 153 | // a font-face block 154 | if ($this->literal('@font-face') && $this->literal('{')) { 155 | $b = $this->pushSpecialBlock('@font-face'); 156 | return true; 157 | } else { 158 | $this->seek($s); 159 | } 160 | 161 | // charset 162 | if ($this->literal('@charset') && $this->propertyValue($value) && 163 | $this->end()) 164 | { 165 | $this->append(array('charset', $value)); 166 | return true; 167 | } else { 168 | $this->seek($s); 169 | } 170 | 171 | 172 | // media 173 | if ($this->literal('@media') && $this->mediaTypes($types) && 174 | $this->literal('{')) 175 | { 176 | $b = $this->pushSpecialBlock('@media'); 177 | $b->media = $types; 178 | return true; 179 | } else { 180 | $this->seek($s); 181 | } 182 | 183 | // css animations 184 | if ($this->match('(@(-[a-z]+-)?keyframes)', $m) && 185 | $this->propertyValue($value) && $this->literal('{')) 186 | { 187 | $b = $this->pushSpecialBlock(trim($m[0])); 188 | $b->keyframes = $value; 189 | return true; 190 | } else { 191 | $this->seek($s); 192 | } 193 | } 194 | 195 | if (isset($this->env->keyframes)) { 196 | if ($this->match("(to|from|[0-9]+%)", $m) && $this->literal('{')) { 197 | $this->pushSpecialBlock($m[1]); 198 | return true; 199 | } else { 200 | $this->seek($s); 201 | } 202 | } 203 | 204 | // setting a variable 205 | if ($this->variable($name) && $this->assign() && 206 | $this->propertyValue($value) && $this->end()) 207 | { 208 | $this->append(array('assign', $this->vPrefix.$name, $value)); 209 | return true; 210 | } else { 211 | $this->seek($s); 212 | } 213 | 214 | if ($this->import($url, $media)) { 215 | // don't check .css files 216 | if (empty($media) && substr_compare($url, '.css', -4, 4) !== 0) { 217 | if ($this->importDisabled) { 218 | $this->append(array('raw', '/* import disabled */')); 219 | } else { 220 | $path = $this->findImport($url); 221 | if (!is_null($path)) { 222 | $this->append(array('import', $path)); 223 | return true; 224 | } 225 | } 226 | } 227 | 228 | $this->append(array('raw', '@import url("'.$url.'")'. 229 | ($media ? ' '.$media : '').';')); 230 | return true; 231 | } 232 | 233 | // opening parametric mixin 234 | if ($this->tag($tag, true) && $this->argumentDef($args) && 235 | $this->literal('{')) 236 | { 237 | $block = $this->pushBlock($this->fixTags(array($tag))); 238 | $block->args = $args; 239 | return true; 240 | } else { 241 | $this->seek($s); 242 | } 243 | 244 | // opening a simple block 245 | if ($this->tags($tags) && $this->literal('{')) { 246 | $tags = $this->fixTags($tags); 247 | $this->pushBlock($tags); 248 | return true; 249 | } else { 250 | $this->seek($s); 251 | } 252 | 253 | // closing a block 254 | if ($this->literal('}')) { 255 | try { 256 | $block = $this->pop(); 257 | } catch (exception $e) { 258 | $this->seek($s); 259 | $this->throwParseError($e->getMessage()); 260 | } 261 | 262 | $hidden = true; 263 | if (!isset($block->args)) foreach ($block->tags as $tag) { 264 | if ($tag{0} != $this->mPrefix) { 265 | $hidden = false; 266 | break; 267 | } 268 | } 269 | 270 | if (!$hidden) $this->append(array('block', $block)); 271 | foreach ($block->tags as $tag) { 272 | if (isset($this->env->children[$tag])) { 273 | $block = $this->mergeBlock($this->env->children[$tag], $block); 274 | } 275 | $this->env->children[$tag] = $block; 276 | } 277 | 278 | return true; 279 | } 280 | 281 | // mixin 282 | if ($this->mixinTags($tags) && 283 | ($this->argumentValues($argv) || true) && $this->end()) 284 | { 285 | $tags = $this->fixTags($tags); 286 | $this->append(array('mixin', $tags, $argv)); 287 | return true; 288 | } else { 289 | $this->seek($s); 290 | } 291 | 292 | // spare ; 293 | if ($this->literal(';')) return true; 294 | 295 | return false; // got nothing, throw error 296 | } 297 | 298 | function fixTags($tags) { 299 | // move @ tags out of variable namespace 300 | foreach ($tags as &$tag) { 301 | if ($tag{0} == $this->vPrefix) $tag[0] = $this->mPrefix; 302 | } 303 | return $tags; 304 | } 305 | 306 | // attempts to find the path of an import url, returns null for css files 307 | function findImport($url) { 308 | foreach ((array)$this->importDir as $dir) { 309 | $full = $dir.(substr($dir, -1) != '/' ? '/' : '').$url; 310 | if ($this->fileExists($file = $full.'.less') || $this->fileExists($file = $full)) { 311 | return $file; 312 | } 313 | } 314 | 315 | return null; 316 | } 317 | 318 | function fileExists($name) { 319 | // sym link workaround 320 | return file_exists($name) || file_exists(realpath(preg_replace('/\w+\/\.\.\//', '', $name))); 321 | } 322 | 323 | // a list of expressions 324 | function expressionList(&$exps) { 325 | $values = array(); 326 | 327 | while ($this->expression($exp)) { 328 | $values[] = $exp; 329 | } 330 | 331 | if (count($values) == 0) return false; 332 | 333 | $exps = $this->compressList($values, ' '); 334 | return true; 335 | } 336 | 337 | /** 338 | * Attempt to consume an expression. 339 | * @link http://en.wikipedia.org/wiki/Operator-precedence_parser#Pseudo-code 340 | */ 341 | function expression(&$out) { 342 | $s = $this->seek(); 343 | if ($this->literal('(') && ($this->inExp = $this->inParens = true) && $this->expression($exp) && $this->literal(')')) { 344 | $lhs = $exp; 345 | } elseif ($this->seek($s) && $this->value($val)) { 346 | $lhs = $val; 347 | } else { 348 | $this->inParens = $this->inExp = false; 349 | $this->seek($s); 350 | return false; 351 | } 352 | 353 | $out = $this->expHelper($lhs, 0); 354 | $this->inParens = $this->inExp = false; 355 | return true; 356 | } 357 | 358 | /** 359 | * recursively parse infix equation with $lhs at precedence $minP 360 | */ 361 | function expHelper($lhs, $minP) { 362 | $this->inExp = true; 363 | $ss = $this->seek(); 364 | 365 | // if the if there was whitespace before the operator, then we require whitespace after 366 | // the operator for it to be a mathematical operator. 367 | 368 | $needWhite = false; 369 | if (!$this->inParens && preg_match('/\s/', $this->buffer{$this->count - 1})) { 370 | $needWhite = true; 371 | } 372 | 373 | // try to find a valid operator 374 | while ($this->match(self::$operatorString.($needWhite ? '\s' : ''), $m) && self::$precedence[$m[1]] >= $minP) { 375 | // get rhs 376 | $s = $this->seek(); 377 | $p = $this->inParens; 378 | if ($this->literal('(') && ($this->inParens = true) && $this->expression($exp) && $this->literal(')')) { 379 | $this->inParens = $p; 380 | $rhs = $exp; 381 | } else { 382 | $this->inParens = $p; 383 | if ($this->seek($s) && $this->value($val)) { 384 | $rhs = $val; 385 | } else { 386 | break; 387 | } 388 | } 389 | 390 | // peek for next operator to see what to do with rhs 391 | if ($this->peek(self::$operatorString, $next) && self::$precedence[$next[1]] > $minP) { 392 | $rhs = $this->expHelper($rhs, self::$precedence[$next[1]]); 393 | } 394 | 395 | // don't evaluate yet if it is dynamic 396 | if (in_array($rhs[0], self::$dtypes) || in_array($lhs[0], self::$dtypes)) 397 | $lhs = array('expression', $m[1], $lhs, $rhs); 398 | else 399 | $lhs = $this->evaluate($m[1], $lhs, $rhs); 400 | 401 | $ss = $this->seek(); 402 | 403 | $needWhite = false; 404 | if (!$this->inParens && preg_match('/\s/', $this->buffer{$this->count - 1})) { 405 | $needWhite = true; 406 | } 407 | } 408 | $this->seek($ss); 409 | 410 | return $lhs; 411 | } 412 | 413 | // consume a list of values for a property 414 | function propertyValue(&$value) { 415 | $values = array(); 416 | 417 | $s = null; 418 | while ($this->expressionList($v)) { 419 | $values[] = $v; 420 | $s = $this->seek(); 421 | if (!$this->literal(',')) break; 422 | } 423 | 424 | if ($s) $this->seek($s); 425 | 426 | if (count($values) == 0) return false; 427 | 428 | $value = $this->compressList($values, ', '); 429 | return true; 430 | } 431 | 432 | // a single value 433 | function value(&$value) { 434 | // try a unit 435 | if ($this->unit($value)) return true; 436 | 437 | // see if there is a negation 438 | $s = $this->seek(); 439 | if ($this->literal('-', false) && $this->variable($vname)) { 440 | $value = array('negative', array('variable', $this->vPrefix.$vname)); 441 | return true; 442 | } else { 443 | $this->seek($s); 444 | } 445 | 446 | // accessor 447 | // must be done before color 448 | // this needs negation too 449 | if ($this->accessor($a)) { 450 | $a[1] = $this->fixTags($a[1]); 451 | $value = $a; 452 | return true; 453 | } 454 | 455 | // color 456 | if ($this->color($value)) return true; 457 | 458 | // css function 459 | // must be done after color 460 | if ($this->func($value)) return true; 461 | 462 | // string 463 | if ($this->string($tmp, $d)) { 464 | $value = array('string', $d.$tmp.$d); 465 | return true; 466 | } 467 | 468 | // try a keyword 469 | if ($this->keyword($word)) { 470 | $value = array('keyword', $word); 471 | return true; 472 | } 473 | 474 | // try a variable 475 | if ($this->variable($vname)) { 476 | $value = array('variable', $this->vPrefix.$vname); 477 | return true; 478 | } 479 | 480 | // unquote string 481 | if ($this->literal("~") && $this->string($value, $d)) { 482 | $value = array("keyword", $value); 483 | return true; 484 | } else { 485 | $this->seek($s); 486 | } 487 | 488 | // css hack: \0 489 | if ($this->literal('\\') && $this->match('([0-9]+)', $m)) { 490 | $value = array('keyword', '\\'.$m[1]); 491 | return true; 492 | } else { 493 | $this->seek($s); 494 | } 495 | 496 | return false; 497 | } 498 | 499 | // an import statement 500 | function import(&$url, &$media) { 501 | $s = $this->seek(); 502 | if (!$this->literal('@import')) return false; 503 | 504 | // @import "something.css" media; 505 | // @import url("something.css") media; 506 | // @import url(something.css) media; 507 | 508 | if ($this->literal('url(')) $parens = true; else $parens = false; 509 | 510 | if (!$this->string($url)) { 511 | if ($parens && $this->to(')', $url)) { 512 | $parens = false; // got em 513 | } else { 514 | $this->seek($s); 515 | return false; 516 | } 517 | } 518 | 519 | if ($parens && !$this->literal(')')) { 520 | $this->seek($s); 521 | return false; 522 | } 523 | 524 | // now the rest is media 525 | return $this->to(';', $media, false, true); 526 | } 527 | 528 | // a list of media types, very lenient 529 | function mediaTypes(&$types) { 530 | if ($this->to('{', $rest, true, true)) { 531 | $types = trim($rest); 532 | return true; 533 | } 534 | 535 | return false; 536 | } 537 | 538 | // a scoped value accessor 539 | // .hello > @scope1 > @scope2['value']; 540 | function accessor(&$var) { 541 | $s = $this->seek(); 542 | 543 | if (!$this->tags($scope, true, '>') || !$this->literal('[')) { 544 | $this->seek($s); 545 | return false; 546 | } 547 | 548 | // either it is a variable or a property 549 | // why is a property wrapped in quotes, who knows! 550 | if ($this->variable($name)) { 551 | $name = $this->vPrefix.$name; 552 | } elseif ($this->literal("'") && $this->keyword($name) && $this->literal("'")) { 553 | // .. $this->count is messed up if we wanted to test another access type 554 | } else { 555 | $this->seek($s); 556 | return false; 557 | } 558 | 559 | if (!$this->literal(']')) { 560 | $this->seek($s); 561 | return false; 562 | } 563 | 564 | $var = array('lookup', $scope, $name); 565 | return true; 566 | } 567 | 568 | // a string 569 | function string(&$string, &$d = null) { 570 | $s = $this->seek(); 571 | if ($this->literal('"', false)) { 572 | $delim = '"'; 573 | } elseif ($this->literal("'", false)) { 574 | $delim = "'"; 575 | } else { 576 | return false; 577 | } 578 | 579 | if (!$this->to($delim, $string)) { 580 | $this->seek($s); 581 | return false; 582 | } 583 | 584 | $d = $delim; 585 | return true; 586 | } 587 | 588 | /** 589 | * Consume a number and optionally a unit. 590 | * Can also consume a font shorthand if it is a simple case. 591 | * $allowed restricts the types that are matched. 592 | */ 593 | function unit(&$unit, $allowed = null) { 594 | $simpleCase = $allowed == null; 595 | if (!$allowed) $allowed = self::$units; 596 | 597 | if ($this->match('(-?[0-9]*(\.)?[0-9]+)('.implode('|', $allowed).')?', $m, !$simpleCase)) { 598 | if (!isset($m[3])) $m[3] = 'number'; 599 | $unit = array($m[3], $m[1]); 600 | 601 | // check for size/height font unit.. should this even be here? 602 | if ($simpleCase) { 603 | $s = $this->seek(); 604 | if (!$this->inExp && $this->literal('/', false) && $this->unit($right, self::$units)) { 605 | $unit = array('keyword', $this->compileValue($unit).'/'.$this->compileValue($right)); 606 | } else { 607 | // get rid of whitespace 608 | $this->seek($s); 609 | $this->match('', $_); 610 | } 611 | } 612 | 613 | return true; 614 | } 615 | 616 | return false; 617 | } 618 | 619 | // a # color 620 | function color(&$out) { 621 | $color = array('color'); 622 | 623 | if ($this->match('(#([0-9a-f]{6})|#([0-9a-f]{3}))', $m)) { 624 | if (isset($m[3])) { 625 | $num = $m[3]; 626 | $width = 16; 627 | } else { 628 | $num = $m[2]; 629 | $width = 256; 630 | } 631 | 632 | $num = hexdec($num); 633 | foreach (array(3,2,1) as $i) { 634 | $t = $num % $width; 635 | $num /= $width; 636 | 637 | $color[$i] = $t * (256/$width) + $t * floor(16/$width); 638 | } 639 | 640 | $out = $color; 641 | return true; 642 | } 643 | 644 | return false; 645 | } 646 | 647 | // consume a list of property values delimited by ; and wrapped in () 648 | function argumentValues(&$args, $delim = ',') { 649 | $s = $this->seek(); 650 | if (!$this->literal('(')) return false; 651 | 652 | $values = array(); 653 | while (true) { 654 | if ($this->expressionList($value)) $values[] = $value; 655 | if (!$this->literal($delim)) break; 656 | else { 657 | if ($value == null) $values[] = null; 658 | $value = null; 659 | } 660 | } 661 | 662 | if (!$this->literal(')')) { 663 | $this->seek($s); 664 | return false; 665 | } 666 | 667 | $args = $values; 668 | return true; 669 | } 670 | 671 | // consume an argument definition list surrounded by () 672 | // each argument is a variable name with optional value 673 | function argumentDef(&$args, $delim = ',') { 674 | $s = $this->seek(); 675 | if (!$this->literal('(')) return false; 676 | 677 | $values = array(); 678 | while ($this->variable($vname)) { 679 | $arg = array($vname); 680 | if ($this->assign() && $this->expressionList($value)) { 681 | $arg[] = $value; 682 | // let the : slide if there is no value 683 | } 684 | 685 | $values[] = $arg; 686 | if (!$this->literal($delim)) break; 687 | } 688 | 689 | if (!$this->literal(')')) { 690 | $this->seek($s); 691 | return false; 692 | } 693 | 694 | $args = $values; 695 | return true; 696 | } 697 | 698 | // consume a list of tags 699 | // this accepts a hanging delimiter 700 | function tags(&$tags, $simple = false, $delim = ',') { 701 | $tags = array(); 702 | while ($this->tag($tt, $simple)) { 703 | $tags[] = $tt; 704 | if (!$this->literal($delim)) break; 705 | } 706 | if (count($tags) == 0) return false; 707 | 708 | return true; 709 | } 710 | 711 | // list of tags of specifying mixin path 712 | // optionally separated by > (lazy, accepts extra >) 713 | function mixinTags(&$tags) { 714 | $s = $this->seek(); 715 | $tags = array(); 716 | while ($this->tag($tt, true)) { 717 | $tags[] = $tt; 718 | $this->literal(">"); 719 | } 720 | 721 | if (count($tags) == 0) return false; 722 | 723 | return true; 724 | } 725 | 726 | // a bracketed value (contained within in a tag definition) 727 | function tagBracket(&$value) { 728 | $s = $this->seek(); 729 | if ($this->literal('[') && $this->to(']', $c, true) && $this->literal(']', false)) { 730 | $value = '['.$c.']'; 731 | // whitespace? 732 | if ($this->match('', $_)) $value .= $_[0]; 733 | 734 | // escape parent selector 735 | $value = str_replace($this->parentSelector, "&&", $value); 736 | return true; 737 | } 738 | 739 | $this->seek($s); 740 | return false; 741 | } 742 | 743 | // a single tag 744 | function tag(&$tag, $simple = false) { 745 | if ($simple) 746 | $chars = '^,:;{}\][>\(\) "\''; 747 | else 748 | $chars = '^,;{}["\''; 749 | 750 | $tag = ''; 751 | while ($this->tagBracket($first)) $tag .= $first; 752 | while ($this->match('(['.$chars.'0-9]['.$chars.']*)', $m)) { 753 | $tag .= $m[1]; 754 | if ($simple) break; 755 | 756 | while ($this->tagBracket($brack)) $tag .= $brack; 757 | } 758 | $tag = trim($tag); 759 | if ($tag == '') return false; 760 | 761 | return true; 762 | } 763 | 764 | // a css function 765 | function func(&$func) { 766 | $s = $this->seek(); 767 | 768 | if ($this->match('(%|[\w\-_][\w\-_:\.]*)', $m) && $this->literal('(')) { 769 | $fname = $m[1]; 770 | if ($fname == 'url') { 771 | $this->to(')', $content, true); 772 | $args = array('string', $content); 773 | } else { 774 | $args = array(); 775 | while (true) { 776 | $ss = $this->seek(); 777 | if ($this->keyword($name) && $this->literal('=') && $this->expressionList($value)) { 778 | $args[] = array('list', '=', array(array('keyword', $name), $value)); 779 | } else { 780 | $this->seek($ss); 781 | if ($this->expressionList($value)) { 782 | $args[] = $value; 783 | } 784 | } 785 | 786 | if (!$this->literal(',')) break; 787 | } 788 | $args = array('list', ',', $args); 789 | } 790 | 791 | if ($this->literal(')')) { 792 | $func = array('function', $fname, $args); 793 | return true; 794 | } 795 | } 796 | 797 | $this->seek($s); 798 | return false; 799 | } 800 | 801 | // consume a less variable 802 | function variable(&$name) { 803 | $s = $this->seek(); 804 | if ($this->literal($this->vPrefix, false) && $this->keyword($name)) { 805 | return true; 806 | } 807 | 808 | return false; 809 | } 810 | 811 | /** 812 | * Consume an assignment operator 813 | * Can optionally take a name that will be set to the current property name 814 | */ 815 | function assign($name = null) { 816 | if ($name) $this->currentProperty = $name; 817 | return $this->literal(':') || $this->literal('='); 818 | } 819 | 820 | // consume a keyword 821 | function keyword(&$word) { 822 | if ($this->match('([\w_\-\*!"][\w\-_"]*)', $m)) { 823 | $word = $m[1]; 824 | return true; 825 | } 826 | return false; 827 | } 828 | 829 | // consume an end of statement delimiter 830 | function end() { 831 | if ($this->literal(';')) 832 | return true; 833 | elseif ($this->count == strlen($this->buffer) || $this->buffer{$this->count} == '}') { 834 | // if there is end of file or a closing block next then we don't need a ; 835 | return true; 836 | } 837 | return false; 838 | } 839 | 840 | function compressList($items, $delim) { 841 | if (count($items) == 1) return $items[0]; 842 | else return array('list', $delim, $items); 843 | } 844 | 845 | // just do a shallow propety merge, seems to be what lessjs does 846 | function mergeBlock($target, $from) { 847 | $target = clone $target; 848 | $target->props = array_merge($target->props, $from->props); 849 | return $target; 850 | } 851 | 852 | /** 853 | * Recursively compiles a block. 854 | * @param $block the block 855 | * @param $parentTags the tags of the block that contained this one 856 | * 857 | * A block is analogous to a CSS block in most cases. A single less document 858 | * is encapsulated in a block when parsed, but it does not have parent tags 859 | * so all of it's children appear on the root level when compiled. 860 | * 861 | * Blocks are made up of props and children. 862 | * 863 | * Props are property instructions, array tuples which describe an action 864 | * to be taken, eg. write a property, set a variable, mixin a block. 865 | * 866 | * The children of a block are just all the blocks that are defined within. 867 | * 868 | * Compiling the block involves pushing a fresh environment on the stack, 869 | * and iterating through the props, compiling each one. 870 | * 871 | * See lessc::compileProp() 872 | * 873 | */ 874 | function compileBlock($block, $parent_tags = null) { 875 | $isRoot = $parent_tags == null && $block->tags == null; 876 | 877 | $indent = str_repeat($this->indentChar, $this->indentLevel); 878 | 879 | if (!empty($block->no_multiply)) { 880 | $special_block = true; 881 | $this->indentLevel++; 882 | $tags = array(); 883 | } else { 884 | $special_block = false; 885 | $tags = $this->multiplyTags($parent_tags, $block->tags); 886 | } 887 | 888 | $this->pushEnv(); 889 | $lines = array(); 890 | $blocks = array(); 891 | foreach ($block->props as $prop) { 892 | $this->compileProp($prop, $block, $tags, $lines, $blocks); 893 | } 894 | 895 | $this->pop(); 896 | 897 | $nl = $isRoot ? "\n".$indent : 898 | "\n".$indent.$this->indentChar; 899 | 900 | ob_start(); 901 | 902 | if ($special_block) { 903 | $this->indentLevel--; 904 | if (isset($block->media)) { 905 | echo "@media ".$block->media; 906 | } elseif (isset($block->keyframes)) { 907 | echo $block->tags[0]." ". 908 | $this->compileValue($this->reduce($block->keyframes)); 909 | } else { 910 | list($name) = $block->tags; 911 | echo $indent.$name; 912 | } 913 | 914 | echo ' {'.(count($lines) > 0 ? $nl : "\n"); 915 | } 916 | 917 | // dump it 918 | if (count($lines) > 0) { 919 | if (!$special_block && !$isRoot) { 920 | echo $indent.implode(", ", $tags); 921 | if (count($lines) > 1) echo " {".$nl; 922 | else echo " { "; 923 | } 924 | 925 | echo implode($nl, $lines); 926 | 927 | if (!$special_block && !$isRoot) { 928 | if (count($lines) > 1) echo "\n".$indent."}\n"; 929 | else echo " }\n"; 930 | } else echo "\n"; 931 | } 932 | 933 | foreach ($blocks as $b) echo $b; 934 | 935 | if ($special_block) { 936 | echo $indent."}\n"; 937 | } 938 | 939 | return ob_get_clean(); 940 | } 941 | 942 | 943 | // find the fully qualified tags for a block and its parent's tags 944 | function multiplyTags($parents, $current) { 945 | if ($parents == null) return $current; 946 | 947 | $tags = array(); 948 | foreach ($parents as $ptag) { 949 | foreach ($current as $tag) { 950 | // inject parent in place of parent selector, ignoring escaped valuews 951 | $count = 0; 952 | $parts = explode("&&", $tag); 953 | 954 | foreach ($parts as $i => $chunk) { 955 | $parts[$i] = str_replace($this->parentSelector, $ptag, $chunk, $c); 956 | $count += $c; 957 | } 958 | 959 | $tag = implode("&", $parts); 960 | 961 | if ($count > 0) { 962 | $tags[] = trim($tag); 963 | } else { 964 | $tags[] = trim($ptag . ' ' . $tag); 965 | } 966 | } 967 | } 968 | 969 | return $tags; 970 | } 971 | 972 | // attempt to find block pointed at by path within search_in or its parent 973 | function findBlock($search_in, $path, $seen=array()) { 974 | if ($search_in == null) return null; 975 | if (isset($seen[$search_in->id])) return null; 976 | $seen[$search_in->id] = true; 977 | 978 | $name = $path[0]; 979 | 980 | if (isset($search_in->children[$name])) { 981 | $block = $search_in->children[$name]; 982 | if (count($path) == 1) { 983 | return $block; 984 | } else { 985 | return $this->findBlock($block, array_slice($path, 1), $seen); 986 | } 987 | } else { 988 | if ($search_in->parent === $search_in) return null; 989 | return $this->findBlock($search_in->parent, $path, $seen); 990 | } 991 | } 992 | 993 | // sets all argument names in $args to either the default value 994 | // or the one passed in through $values 995 | function zipSetArgs($args, $values) { 996 | $i = 0; 997 | $assigned_values = array(); 998 | foreach ($args as $a) { 999 | if ($i < count($values) && !is_null($values[$i])) { 1000 | $value = $values[$i]; 1001 | } elseif (isset($a[1])) { 1002 | $value = $a[1]; 1003 | } else $value = null; 1004 | 1005 | $value = $this->reduce($value); 1006 | $this->set($this->vPrefix.$a[0], $value); 1007 | $assigned_values[] = $value; 1008 | $i++; 1009 | } 1010 | 1011 | // copy over any extra default args 1012 | for ($i = count($values); $i < count($assigned_values); $i++) { 1013 | $values[] = $assigned_values[$i]; 1014 | } 1015 | 1016 | $this->env->arguments = $values; 1017 | } 1018 | 1019 | // compile a prop and update $lines or $blocks appropriately 1020 | function compileProp($prop, $block, $tags, &$_lines, &$_blocks) { 1021 | switch ($prop[0]) { 1022 | case 'assign': 1023 | list(, $name, $value) = $prop; 1024 | if ($name[0] == $this->vPrefix) { 1025 | $this->set($name, $this->reduce($value)); 1026 | } else { 1027 | $_lines[] = "$name:". 1028 | $this->compileValue($this->reduce($value)).";"; 1029 | } 1030 | break; 1031 | case 'block': 1032 | list(, $child) = $prop; 1033 | $_blocks[] = $this->compileBlock($child, $tags); 1034 | break; 1035 | case 'mixin': 1036 | list(, $path, $args) = $prop; 1037 | 1038 | $mixin = $this->findBlock($block, $path); 1039 | if (is_null($mixin)) { 1040 | // echo "failed to find block: ".implode(" > ", $path)."\n"; 1041 | break; // throw error here?? 1042 | } 1043 | 1044 | $have_args = false; 1045 | if (isset($mixin->args)) { 1046 | $have_args = true; 1047 | $this->pushEnv(); 1048 | $this->zipSetArgs($mixin->args, $args); 1049 | } 1050 | 1051 | $old_parent = $mixin->parent; 1052 | $mixin->parent = $block; 1053 | 1054 | foreach ($mixin->props as $sub_prop) { 1055 | $this->compileProp($sub_prop, $mixin, $tags, $_lines, $_blocks); 1056 | } 1057 | 1058 | $mixin->parent = $old_parent; 1059 | 1060 | if ($have_args) $this->pop(); 1061 | 1062 | break; 1063 | case 'raw': 1064 | $_lines[] = $prop[1]; 1065 | break; 1066 | case 'import': 1067 | list(, $path) = $prop; 1068 | $this->addParsedFile($path); 1069 | $root = $this->createChild($path)->parseTree(); 1070 | 1071 | $root->parent = $block; 1072 | foreach ($root->props as $sub_prop) { 1073 | $this->compileProp($sub_prop, $root, $tags, $_lines, $_blocks); 1074 | } 1075 | 1076 | // inject imported blocks into this block, local will overwrite import 1077 | $block->children = array_merge($root->children, $block->children); 1078 | break; 1079 | case 'charset': 1080 | list(, $value) = $prop; 1081 | $_lines[] = '@charset '.$this->compileValue($this->reduce($value)).';'; 1082 | break; 1083 | default: 1084 | echo "unknown op: {$prop[0]}\n"; 1085 | throw new exception(); 1086 | } 1087 | } 1088 | 1089 | 1090 | /** 1091 | * Compiles a primitive value into a CSS property value. 1092 | * 1093 | * Values in lessphp are typed by being wrapped in arrays, their format is 1094 | * typically: 1095 | * 1096 | * array(type, contents [, additional_contents]*) 1097 | * 1098 | * Will not work on non reduced values (expressions, variables, etc) 1099 | */ 1100 | function compileValue($value) { 1101 | switch ($value[0]) { 1102 | case 'list': 1103 | // [1] - delimiter 1104 | // [2] - array of values 1105 | return implode($value[1], array_map(array($this, 'compileValue'), $value[2])); 1106 | case 'keyword': 1107 | // [1] - the keyword 1108 | case 'number': 1109 | // [1] - the number 1110 | return $value[1]; 1111 | case 'string': 1112 | // [1] - contents of string (includes quotes) 1113 | 1114 | // search for inline variables to replace 1115 | $replace = array(); 1116 | if (preg_match_all('/'.$this->preg_quote($this->vPrefix).'\{([\w-_][0-9\w-_]*)\}/', $value[1], $m)) { 1117 | foreach ($m[1] as $name) { 1118 | if (!isset($replace[$name])) 1119 | $replace[$name] = $this->compileValue($this->reduce(array('variable', $this->vPrefix . $name))); 1120 | } 1121 | } 1122 | 1123 | foreach ($replace as $var=>$val) { 1124 | if ($this->quoted($val)) { 1125 | $val = substr($val, 1, -1); 1126 | } 1127 | $value[1] = str_replace($this->vPrefix. '{'.$var.'}', $val, $value[1]); 1128 | } 1129 | 1130 | return $value[1]; 1131 | case 'color': 1132 | // [1] - red component (either number for a %) 1133 | // [2] - green component 1134 | // [3] - blue component 1135 | // [4] - optional alpha component 1136 | if (count($value) == 5) { // rgba 1137 | return 'rgba('.$value[1].','.$value[2].','.$value[3].','.$value[4].')'; 1138 | } 1139 | return sprintf("#%02x%02x%02x", $value[1], $value[2], $value[3]); 1140 | case 'function': 1141 | // [1] - function name 1142 | // [2] - some value representing arguments 1143 | 1144 | // see if function evaluates to something else 1145 | $value = $this->reduce($value); 1146 | if ($value[0] == 'function') { 1147 | return $value[1].'('.$this->compileValue($value[2]).')'; 1148 | } 1149 | else return $this->compileValue($value); 1150 | default: // assumed to be unit 1151 | return $value[1].$value[0]; 1152 | } 1153 | } 1154 | 1155 | function lib_rgbahex($color) { 1156 | if ($color[0] != 'color') 1157 | throw new exception("color expected for rgbahex"); 1158 | 1159 | return sprintf("#%02x%02x%02x%02x", 1160 | isset($color[4]) ? $color[4]*255 : 0, 1161 | $color[1],$color[2], $color[3]); 1162 | } 1163 | 1164 | // utility func to unquote a string 1165 | function lib_e($arg) { 1166 | switch ($arg[0]) { 1167 | case "list": 1168 | $items = $arg[2]; 1169 | if (isset($items[0])) { 1170 | return $this->lib_e($items[0]); 1171 | } 1172 | return ""; 1173 | case "string": 1174 | $str = $this->compileValue($arg); 1175 | return substr($str, 1, -1); 1176 | default: 1177 | return $this->compileValue($arg); 1178 | } 1179 | } 1180 | 1181 | function lib__sprintf($args) { 1182 | if ($args[0] != "list") return $args; 1183 | $values = $args[2]; 1184 | $source = $this->reduce(array_shift($values)); 1185 | if ($source[0] != "string") { 1186 | return $source; 1187 | } 1188 | 1189 | $str = $source[1]; 1190 | $i = 0; 1191 | if (preg_match_all('/%[dsa]/', $str, $m)) { 1192 | foreach ($m[0] as $match) { 1193 | $val = isset($values[$i]) ? $this->reduce($values[$i]) : array('keyword', ''); 1194 | $i++; 1195 | switch ($match[1]) { 1196 | case "s": 1197 | if ($val[0] == "string") { 1198 | $rep = substr($val[1], 1, -1); 1199 | break; 1200 | } 1201 | default: 1202 | $rep = $this->compileValue($val); 1203 | } 1204 | $str = preg_replace('/'.$this->preg_quote($match).'/', $rep, $str, 1); 1205 | } 1206 | } 1207 | 1208 | return array('string', $str); 1209 | } 1210 | 1211 | function lib_floor($arg) { 1212 | return floor($arg[1]); 1213 | } 1214 | 1215 | function lib_round($arg) { 1216 | return round($arg[1]); 1217 | } 1218 | 1219 | // is a string surrounded in quotes? returns the quoting char if true 1220 | function quoted($s) { 1221 | if (preg_match('/^("|\').*?\1$/', $s, $m)) 1222 | return $m[1]; 1223 | else return false; 1224 | } 1225 | 1226 | /** 1227 | * Helper function to get argurments for color functions 1228 | * accepts invalid input, non colors interpreted to black 1229 | */ 1230 | function colorArgs($args) { 1231 | if ($args[0] != 'list' || count($args[2]) < 2) { 1232 | return array(array('color', 0, 0, 0)); 1233 | } 1234 | list($color, $delta) = $args[2]; 1235 | $color = $this->coerceColor($color); 1236 | if (is_null($color)) 1237 | $color = array('color', 0, 0, 0); 1238 | 1239 | $delta = floatval($delta[1]); 1240 | 1241 | return array($color, $delta); 1242 | } 1243 | 1244 | function lib_darken($args) { 1245 | list($color, $delta) = $this->colorArgs($args); 1246 | 1247 | $hsl = $this->toHSL($color); 1248 | $hsl[3] = $this->clamp($hsl[3] - $delta, 100); 1249 | return $this->toRGB($hsl); 1250 | } 1251 | 1252 | function lib_lighten($args) { 1253 | list($color, $delta) = $this->colorArgs($args); 1254 | 1255 | $hsl = $this->toHSL($color); 1256 | $hsl[3] = $this->clamp($hsl[3] + $delta, 100); 1257 | return $this->toRGB($hsl); 1258 | } 1259 | 1260 | function lib_saturate($args) { 1261 | list($color, $delta) = $this->colorArgs($args); 1262 | 1263 | $hsl = $this->toHSL($color); 1264 | $hsl[2] = $this->clamp($hsl[2] + $delta, 100); 1265 | return $this->toRGB($hsl); 1266 | } 1267 | 1268 | function lib_desaturate($args) { 1269 | list($color, $delta) = $this->colorArgs($args); 1270 | 1271 | $hsl = $this->toHSL($color); 1272 | $hsl[2] = $this->clamp($hsl[2] - $delta, 100); 1273 | return $this->toRGB($hsl); 1274 | } 1275 | 1276 | function lib_spin($args) { 1277 | list($color, $delta) = $this->colorArgs($args); 1278 | 1279 | $hsl = $this->toHSL($color); 1280 | $hsl[1] = $this->clamp($hsl[1] + $delta, 360); 1281 | return $this->toRGB($hsl); 1282 | } 1283 | 1284 | function lib_fadeout($args) { 1285 | list($color, $delta) = $this->colorArgs($args); 1286 | $color[4] = $this->clamp((isset($color[4]) ? $color[4] : 1) - $delta/100); 1287 | return $color; 1288 | } 1289 | 1290 | function lib_fadein($args) { 1291 | list($color, $delta) = $this->colorArgs($args); 1292 | $color[4] = $this->clamp((isset($color[4]) ? $color[4] : 1) + $delta/100); 1293 | return $color; 1294 | } 1295 | 1296 | function lib_hue($color) { 1297 | if ($color[0] != 'color') return 0; 1298 | $hsl = $this->toHSL($color); 1299 | return round($hsl[1]); 1300 | } 1301 | 1302 | function lib_saturation($color) { 1303 | if ($color[0] != 'color') return 0; 1304 | $hsl = $this->toHSL($color); 1305 | return round($hsl[2]); 1306 | } 1307 | 1308 | function lib_lightness($color) { 1309 | if ($color[0] != 'color') return 0; 1310 | $hsl = $this->toHSL($color); 1311 | return round($hsl[3]); 1312 | } 1313 | 1314 | // get the alpha of a color 1315 | // defaults to 1 for non-colors or colors without an alpha 1316 | function lib_alpha($color) { 1317 | if ($color[0] != 'color') return 1; 1318 | return isset($color[4]) ? $color[4] : 1; 1319 | } 1320 | 1321 | // set the alpha of the color 1322 | function lib_fade($args) { 1323 | list($color, $alpha) = $this->colorArgs($args); 1324 | $color[4] = $this->clamp($alpha / 100.0); 1325 | return $color; 1326 | } 1327 | 1328 | function lib_percentage($number) { 1329 | return array('%', $number[1]*100); 1330 | } 1331 | 1332 | // mixes two colors by weight 1333 | // mix(@color1, @color2, @weight); 1334 | // http://sass-lang.com/docs/yardoc/Sass/Script/Functions.html#mix-instance_method 1335 | function lib_mix($args) { 1336 | if ($args[0] != "list") 1337 | throw new exception("mix expects (color1, color2, weight)"); 1338 | 1339 | list($first, $second, $weight) = $args[2]; 1340 | $first = $this->assertColor($first); 1341 | $second = $this->assertColor($second); 1342 | 1343 | $first_a = $this->lib_alpha($first); 1344 | $second_a = $this->lib_alpha($second); 1345 | $weight = $weight[1] / 100.0; 1346 | 1347 | $w = $weight * 2 - 1; 1348 | $a = $first_a - $second_a; 1349 | 1350 | $w1 = (($w * $a == -1 ? $w : ($w + $a)/(1 + $w * $a)) + 1) / 2.0; 1351 | $w2 = 1.0 - $w1; 1352 | 1353 | $new = array('color', 1354 | $w1 * $first[1] + $w2 * $second[1], 1355 | $w1 * $first[2] + $w2 * $second[2], 1356 | $w1 * $first[3] + $w2 * $second[3], 1357 | ); 1358 | 1359 | if ($first_a != 1.0 || $second_a != 1.0) { 1360 | $new[] = $first_a * $p + $second_a * ($p - 1); 1361 | } 1362 | 1363 | return $this->fixColor($new); 1364 | } 1365 | 1366 | function assertColor($value, $error = "expected color value") { 1367 | $color = $this->coerceColor($value); 1368 | if (is_null($color)) throw new exception($error); 1369 | return $color; 1370 | } 1371 | 1372 | function toHSL($color) { 1373 | if ($color[0] == 'hsl') return $color; 1374 | 1375 | $r = $color[1] / 255; 1376 | $g = $color[2] / 255; 1377 | $b = $color[3] / 255; 1378 | 1379 | $min = min($r, $g, $b); 1380 | $max = max($r, $g, $b); 1381 | 1382 | $L = ($min + $max) / 2; 1383 | if ($min == $max) { 1384 | $S = $H = 0; 1385 | } else { 1386 | if ($L < 0.5) 1387 | $S = ($max - $min)/($max + $min); 1388 | else 1389 | $S = ($max - $min)/(2.0 - $max - $min); 1390 | 1391 | if ($r == $max) $H = ($g - $b)/($max - $min); 1392 | elseif ($g == $max) $H = 2.0 + ($b - $r)/($max - $min); 1393 | elseif ($b == $max) $H = 4.0 + ($r - $g)/($max - $min); 1394 | 1395 | } 1396 | 1397 | $out = array('hsl', 1398 | ($H < 0 ? $H + 6 : $H)*60, 1399 | $S*100, 1400 | $L*100, 1401 | ); 1402 | 1403 | if (count($color) > 4) $out[] = $color[4]; // copy alpha 1404 | return $out; 1405 | } 1406 | 1407 | function toRGB_helper($comp, $temp1, $temp2) { 1408 | if ($comp < 0) $comp += 1.0; 1409 | elseif ($comp > 1) $comp -= 1.0; 1410 | 1411 | if (6 * $comp < 1) return $temp1 + ($temp2 - $temp1) * 6 * $comp; 1412 | if (2 * $comp < 1) return $temp2; 1413 | if (3 * $comp < 2) return $temp1 + ($temp2 - $temp1)*((2/3) - $comp) * 6; 1414 | 1415 | return $temp1; 1416 | } 1417 | 1418 | /** 1419 | * Converts an hsl array into a color value in rgb. 1420 | * Expects H to be in range of 0 to 360, S and L in 0 to 100 1421 | */ 1422 | function toRGB($color) { 1423 | if ($color == 'color') return $color; 1424 | 1425 | $H = $color[1] / 360; 1426 | $S = $color[2] / 100; 1427 | $L = $color[3] / 100; 1428 | 1429 | if ($S == 0) { 1430 | $r = $g = $b = $L; 1431 | } else { 1432 | $temp2 = $L < 0.5 ? 1433 | $L*(1.0 + $S) : 1434 | $L + $S - $L * $S; 1435 | 1436 | $temp1 = 2.0 * $L - $temp2; 1437 | 1438 | $r = $this->toRGB_helper($H + 1/3, $temp1, $temp2); 1439 | $g = $this->toRGB_helper($H, $temp1, $temp2); 1440 | $b = $this->toRGB_helper($H - 1/3, $temp1, $temp2); 1441 | } 1442 | 1443 | $out = array('color', round($r*255), round($g*255), round($b*255)); 1444 | if (count($color) > 4) $out[] = $color[4]; // copy alpha 1445 | return $out; 1446 | } 1447 | 1448 | function clamp($v, $max = 1, $min = 0) { 1449 | return min($max, max($min, $v)); 1450 | } 1451 | 1452 | /** 1453 | * Convert the rgb, rgba, hsl color literals of function type 1454 | * as returned by the parser into values of color type. 1455 | */ 1456 | function funcToColor($func) { 1457 | $fname = $func[1]; 1458 | if ($func[2][0] != 'list') return false; // need a list of arguments 1459 | $rawComponents = $func[2][2]; 1460 | 1461 | if ($fname == 'hsl' || $fname == 'hsla') { 1462 | $hsl = array('hsl'); 1463 | $i = 0; 1464 | foreach ($rawComponents as $c) { 1465 | $val = $this->reduce($c); 1466 | $val = isset($val[1]) ? floatval($val[1]) : 0; 1467 | 1468 | if ($i == 0) $clamp = 360; 1469 | elseif ($i < 4) $clamp = 100; 1470 | else $clamp = 1; 1471 | 1472 | $hsl[] = $this->clamp($val, $clamp); 1473 | $i++; 1474 | } 1475 | 1476 | while (count($hsl) < 4) $hsl[] = 0; 1477 | return $this->toRGB($hsl); 1478 | 1479 | } elseif ($fname == 'rgb' || $fname == 'rgba') { 1480 | $components = array(); 1481 | $i = 1; 1482 | foreach ($rawComponents as $c) { 1483 | $c = $this->reduce($c); 1484 | if ($i < 4) { 1485 | if ($c[0] == '%') $components[] = 255 * ($c[1] / 100); 1486 | else $components[] = floatval($c[1]); 1487 | } elseif ($i == 4) { 1488 | if ($c[0] == '%') $components[] = 1.0 * ($c[1] / 100); 1489 | else $components[] = floatval($c[1]); 1490 | } else break; 1491 | 1492 | $i++; 1493 | } 1494 | while (count($components) < 3) $components[] = 0; 1495 | array_unshift($components, 'color'); 1496 | return $this->fixColor($components); 1497 | } 1498 | 1499 | return false; 1500 | } 1501 | 1502 | // reduce a delayed type to its final value 1503 | // dereference variables and solve equations 1504 | function reduce($var, $defaultValue = array('number', 0)) { 1505 | while (in_array($var[0], self::$dtypes)) { 1506 | if ($var[0] == 'list') { 1507 | foreach ($var[2] as &$value) $value = $this->reduce($value); 1508 | break; 1509 | } elseif ($var[0] == 'expression') { 1510 | $var = $this->evaluate($var[1], $var[2], $var[3]); 1511 | } elseif ($var[0] == 'variable') { 1512 | $var = $this->get($var[1]); 1513 | } elseif ($var[0] == 'lookup') { 1514 | // do accessor here.... 1515 | $var = array('number', 0); 1516 | } elseif ($var[0] == 'function') { 1517 | $color = $this->funcToColor($var); 1518 | if ($color) $var = $color; 1519 | else { 1520 | list($_, $name, $args) = $var; 1521 | if ($name == "%") $name = "_sprintf"; 1522 | $f = isset($this->libFunctions[$name]) ? 1523 | $this->libFunctions[$name] : array($this, 'lib_'.$name); 1524 | 1525 | if (is_callable($f)) { 1526 | if ($args[0] == 'list') 1527 | $args = $this->compressList($args[2], $args[1]); 1528 | 1529 | $var = call_user_func($f, $this->reduce($args)); 1530 | 1531 | // convet to a typed value if the result is a php primitive 1532 | if (is_numeric($var)) $var = array('number', $var); 1533 | elseif (!is_array($var)) $var = array('keyword', $var); 1534 | } else { 1535 | // plain function, reduce args 1536 | $var[2] = $this->reduce($var[2]); 1537 | } 1538 | } 1539 | break; // done reducing after a function 1540 | } elseif ($var[0] == 'negative') { 1541 | $value = $this->reduce($var[1]); 1542 | if (is_numeric($value[1])) { 1543 | $value[1] = -1*$value[1]; 1544 | } 1545 | $var = $value; 1546 | } 1547 | } 1548 | 1549 | return $var; 1550 | } 1551 | 1552 | function coerceColor($value) { 1553 | switch($value[0]) { 1554 | case 'color': return $value; 1555 | case 'keyword': 1556 | $name = $value[1]; 1557 | if (isset(self::$cssColors[$name])) { 1558 | list($r, $g, $b) = explode(',', self::$cssColors[$name]); 1559 | return array('color', $r, $g, $b); 1560 | } 1561 | return null; 1562 | } 1563 | } 1564 | 1565 | // evaluate an expression 1566 | function evaluate($op, $left, $right) { 1567 | $left = $this->reduce($left); 1568 | $right = $this->reduce($right); 1569 | 1570 | if ($left_color = $this->coerceColor($left)) { 1571 | $left = $left_color; 1572 | } 1573 | 1574 | if ($right_color = $this->coerceColor($right)) { 1575 | $right = $right_color; 1576 | } 1577 | 1578 | if ($left[0] == 'color' && $right[0] == 'color') { 1579 | $out = $this->op_color_color($op, $left, $right); 1580 | return $out; 1581 | } 1582 | 1583 | if ($left[0] == 'color') { 1584 | return $this->op_color_number($op, $left, $right); 1585 | } 1586 | 1587 | if ($right[0] == 'color') { 1588 | return $this->op_number_color($op, $left, $right); 1589 | } 1590 | 1591 | // concatenate strings 1592 | if ($op == '+' && $left[0] == 'string') { 1593 | $append = $this->compileValue($right); 1594 | if ($this->quoted($append)) $append = substr($append, 1, -1); 1595 | 1596 | $lhs = $this->compileValue($left); 1597 | if ($q = $this->quoted($lhs)) $lhs = substr($lhs, 1, -1); 1598 | if (!$q) $q = ''; 1599 | 1600 | return array('string', $q.$lhs.$append.$q); 1601 | } 1602 | 1603 | if ($left[0] == 'keyword' || $right[0] == 'keyword' || 1604 | $left[0] == 'string' || $right[0] == 'string') 1605 | { 1606 | // look for negative op 1607 | if ($op == '-') $right[1] = '-'.$right[1]; 1608 | return array('keyword', $this->compileValue($left) .' '. $this->compileValue($right)); 1609 | } 1610 | 1611 | // default to number operation 1612 | return $this->op_number_number($op, $left, $right); 1613 | } 1614 | 1615 | // make sure a color's components don't go out of bounds 1616 | function fixColor($c) { 1617 | foreach (range(1, 3) as $i) { 1618 | if ($c[$i] < 0) $c[$i] = 0; 1619 | if ($c[$i] > 255) $c[$i] = 255; 1620 | $c[$i] = floor($c[$i]); 1621 | } 1622 | 1623 | return $c; 1624 | } 1625 | 1626 | function op_number_color($op, $lft, $rgt) { 1627 | if ($op == '+' || $op = '*') { 1628 | return $this->op_color_number($op, $rgt, $lft); 1629 | } 1630 | } 1631 | 1632 | function op_color_number($op, $lft, $rgt) { 1633 | if ($rgt[0] == '%') $rgt[1] /= 100; 1634 | 1635 | return $this->op_color_color($op, $lft, 1636 | array_fill(1, count($lft) - 1, $rgt[1])); 1637 | } 1638 | 1639 | function op_color_color($op, $left, $right) { 1640 | $out = array('color'); 1641 | $max = count($left) > count($right) ? count($left) : count($right); 1642 | foreach (range(1, $max - 1) as $i) { 1643 | $lval = isset($left[$i]) ? $left[$i] : 0; 1644 | $rval = isset($right[$i]) ? $right[$i] : 0; 1645 | switch ($op) { 1646 | case '+': 1647 | $out[] = $lval + $rval; 1648 | break; 1649 | case '-': 1650 | $out[] = $lval - $rval; 1651 | break; 1652 | case '*': 1653 | $out[] = $lval * $rval; 1654 | break; 1655 | case '%': 1656 | $out[] = $lval % $rval; 1657 | break; 1658 | case '/': 1659 | if ($rval == 0) throw new exception("evaluate error: can't divide by zero"); 1660 | $out[] = $lval / $rval; 1661 | break; 1662 | default: 1663 | throw new exception('evaluate error: color op number failed on op '.$op); 1664 | } 1665 | } 1666 | return $this->fixColor($out); 1667 | } 1668 | 1669 | // operator on two numbers 1670 | function op_number_number($op, $left, $right) { 1671 | if ($right[0] == '%') $right[1] /= 100; 1672 | 1673 | // figure out type 1674 | if ($right[0] == 'number' || $right[0] == '%') $type = $left[0]; 1675 | else $type = $right[0]; 1676 | 1677 | $value = 0; 1678 | switch ($op) { 1679 | case '+': 1680 | $value = $left[1] + $right[1]; 1681 | break; 1682 | case '*': 1683 | $value = $left[1] * $right[1]; 1684 | break; 1685 | case '-': 1686 | $value = $left[1] - $right[1]; 1687 | break; 1688 | case '%': 1689 | $value = $left[1] % $right[1]; 1690 | break; 1691 | case '/': 1692 | if ($right[1] == 0) throw new exception('parse error: divide by zero'); 1693 | $value = $left[1] / $right[1]; 1694 | break; 1695 | default: 1696 | throw new exception('parse error: unknown number operator: '.$op); 1697 | } 1698 | 1699 | return array($type, $value); 1700 | } 1701 | 1702 | 1703 | /* environment functions */ 1704 | 1705 | // push a new block on the stack, used for parsing 1706 | function pushBlock($tags) { 1707 | $b = new stdclass; 1708 | $b->parent = $this->env; 1709 | 1710 | $b->id = self::$nextBlockId++; 1711 | $b->tags = $tags; 1712 | $b->props = array(); 1713 | $b->children = array(); 1714 | 1715 | $this->env = $b; 1716 | return $b; 1717 | } 1718 | 1719 | // push a block that doesn't multiply tags 1720 | function pushSpecialBlock($name) { 1721 | $b = $this->pushBlock(array($name)); 1722 | $b->no_multiply = true; 1723 | return $b; 1724 | } 1725 | 1726 | // used for compiliation variable state 1727 | function pushEnv() { 1728 | $e = new stdclass; 1729 | $e->parent = $this->env; 1730 | 1731 | $this->store = array(); 1732 | 1733 | $this->env = $e; 1734 | return $e; 1735 | } 1736 | 1737 | // pop something off the stack 1738 | function pop() { 1739 | $old = $this->env; 1740 | $this->env = $this->env->parent; 1741 | return $old; 1742 | } 1743 | 1744 | // set something in the current env 1745 | function set($name, $value) { 1746 | $this->env->store[$name] = $value; 1747 | } 1748 | 1749 | // append an property 1750 | function append($prop) { 1751 | $this->env->props[] = $prop; 1752 | } 1753 | 1754 | // get the highest occurrence entry for a name 1755 | function get($name) { 1756 | $current = $this->env; 1757 | 1758 | $is_arguments = $name == $this->vPrefix . 'arguments'; 1759 | while ($current) { 1760 | if ($is_arguments && isset($current->arguments)) { 1761 | return array('list', ' ', $current->arguments); 1762 | } 1763 | 1764 | if (isset($current->store[$name])) 1765 | return $current->store[$name]; 1766 | else 1767 | $current = $current->parent; 1768 | } 1769 | 1770 | return null; 1771 | } 1772 | 1773 | /* raw parsing functions */ 1774 | 1775 | function literal($what, $eatWhitespace = true) { 1776 | // this is here mainly prevent notice from { } string accessor 1777 | if ($this->count >= strlen($this->buffer)) return false; 1778 | 1779 | // shortcut on single letter 1780 | if (!$eatWhitespace && strlen($what) == 1) { 1781 | if ($this->buffer{$this->count} == $what) { 1782 | $this->count++; 1783 | return true; 1784 | } 1785 | else return false; 1786 | } 1787 | 1788 | return $this->match($this->preg_quote($what), $m, $eatWhitespace); 1789 | } 1790 | 1791 | function preg_quote($what) { 1792 | return preg_quote($what, '/'); 1793 | } 1794 | 1795 | // advance counter to next occurrence of $what 1796 | // $until - don't include $what in advance 1797 | function to($what, &$out, $until = false, $allowNewline = false) { 1798 | $validChars = $allowNewline ? "." : "[^\n]"; 1799 | if (!$this->match('('.$validChars.'*?)'.$this->preg_quote($what), $m, !$until)) return false; 1800 | if ($until) $this->count -= strlen($what); // give back $what 1801 | $out = $m[1]; 1802 | return true; 1803 | } 1804 | 1805 | // try to match something on head of buffer 1806 | function match($regex, &$out, $eatWhitespace = true) { 1807 | $r = '/'.$regex.($eatWhitespace ? '\s*' : '').'/Ais'; 1808 | if (preg_match($r, $this->buffer, $out, null, $this->count)) { 1809 | $this->count += strlen($out[0]); 1810 | return true; 1811 | } 1812 | return false; 1813 | } 1814 | 1815 | // match something without consuming it 1816 | function peek($regex, &$out = null) { 1817 | $r = '/'.$regex.'/Ais'; 1818 | $result = preg_match($r, $this->buffer, $out, null, $this->count); 1819 | 1820 | return $result; 1821 | } 1822 | 1823 | // seek to a spot in the buffer or return where we are on no argument 1824 | function seek($where = null) { 1825 | if ($where === null) return $this->count; 1826 | else $this->count = $where; 1827 | return true; 1828 | } 1829 | 1830 | /** 1831 | * Initialize state for a fresh parse 1832 | */ 1833 | protected function prepareParser($buff) { 1834 | $this->env = null; 1835 | $this->expandStack = array(); 1836 | $this->indentLevel = 0; 1837 | $this->count = 0; 1838 | $this->line = 1; 1839 | 1840 | $this->buffer = $this->removeComments($buff); 1841 | $this->pushBlock(null); // set up global scope 1842 | 1843 | // trim whitespace on head 1844 | if (preg_match('/^\s+/', $this->buffer, $m)) { 1845 | $this->line += substr_count($m[0], "\n"); 1846 | $this->buffer = ltrim($this->buffer); 1847 | } 1848 | } 1849 | 1850 | // create a child parser (for compiling an import) 1851 | protected function createChild($fname) { 1852 | $less = new lessc($fname); 1853 | $less->importDir = $this->importDir; 1854 | $less->indentChar = $this->indentChar; 1855 | $less->compat = $this->compat; 1856 | return $less; 1857 | } 1858 | 1859 | // parse code and return intermediate tree 1860 | public function parseTree($str = null) { 1861 | $this->prepareParser(is_null($str) ? $this->buffer : $str); 1862 | while (false !== $this->parseChunk()); 1863 | 1864 | if ($this->count != strlen($this->buffer)) 1865 | $this->throwParseError(); 1866 | 1867 | if (!is_null($this->env->parent)) 1868 | throw new exception('parse error: unclosed block'); 1869 | 1870 | $root = $this->env; 1871 | $this->env = null; 1872 | return $root; 1873 | } 1874 | 1875 | // inject array of unparsed strings into environment as variables 1876 | protected function injectVariables($args) { 1877 | $this->pushEnv(); 1878 | $parser = new lessc(); 1879 | foreach ($args as $name => $str_value) { 1880 | if ($name{0} != '@') $name = '@'.$name; 1881 | $parser->count = 0; 1882 | $parser->buffer = (string)$str_value; 1883 | if (!$parser->propertyValue($value)) { 1884 | throw new Exception("failed to parse passed in variable $name: $str_value"); 1885 | } 1886 | 1887 | $this->set($name, $value); 1888 | } 1889 | } 1890 | 1891 | // parse and compile buffer 1892 | function parse($str = null, $initial_variables = null) { 1893 | $locale = setlocale(LC_NUMERIC, 0); 1894 | setlocale(LC_NUMERIC, "C"); 1895 | $root = $this->parseTree($str); 1896 | 1897 | if ($initial_variables) $this->injectVariables($initial_variables); 1898 | $out = $this->compileBlock($root); 1899 | setlocale(LC_NUMERIC, $locale); 1900 | return $out; 1901 | } 1902 | 1903 | function throwParseError($msg = 'parse error') { 1904 | $line = $this->line + substr_count(substr($this->buffer, 0, $this->count), "\n"); 1905 | if (isset($this->fileName)) { 1906 | $loc = $this->fileName.' on line '.$line; 1907 | } else { 1908 | $loc = "line: ".$line; 1909 | } 1910 | 1911 | if ($this->peek("(.*?)(\n|$)", $m)) 1912 | throw new exception($msg.': failed at `'.$m[1].'` '.$loc); 1913 | } 1914 | 1915 | /** 1916 | * Initialize any static state, can initialize parser for a file 1917 | */ 1918 | function __construct($fname = null, $opts = null) { 1919 | if (!self::$operatorString) { 1920 | self::$operatorString = 1921 | '('.implode('|', array_map(array($this, 'preg_quote'), 1922 | array_keys(self::$precedence))).')'; 1923 | } 1924 | 1925 | if ($fname) { 1926 | if (!is_file($fname)) { 1927 | throw new Exception('load error: failed to find '.$fname); 1928 | } 1929 | $pi = pathinfo($fname); 1930 | 1931 | $this->fileName = $fname; 1932 | $this->importDir = $pi['dirname'].'/'; 1933 | $this->buffer = file_get_contents($fname); 1934 | 1935 | $this->addParsedFile($fname); 1936 | } 1937 | } 1938 | 1939 | public function registerFunction($name, $func) { 1940 | $this->libFunctions[$name] = $func; 1941 | } 1942 | 1943 | public function unregisterFunction($name) { 1944 | unset($this->libFunctions[$name]); 1945 | } 1946 | 1947 | // remove comments from $text 1948 | // todo: make it work for all functions, not just url 1949 | function removeComments($text) { 1950 | $look = array( 1951 | 'url(', '//', '/*', '"', "'" 1952 | ); 1953 | 1954 | $out = ''; 1955 | $min = null; 1956 | $done = false; 1957 | while (true) { 1958 | // find the next item 1959 | foreach ($look as $token) { 1960 | $pos = strpos($text, $token); 1961 | if ($pos !== false) { 1962 | if (!isset($min) || $pos < $min[1]) $min = array($token, $pos); 1963 | } 1964 | } 1965 | 1966 | if (is_null($min)) break; 1967 | 1968 | $count = $min[1]; 1969 | $skip = 0; 1970 | $newlines = 0; 1971 | switch ($min[0]) { 1972 | case 'url(': 1973 | if (preg_match('/url\(.*?\)/', $text, $m, 0, $count)) 1974 | $count += strlen($m[0]) - strlen($min[0]); 1975 | break; 1976 | case '"': 1977 | case "'": 1978 | if (preg_match('/'.$min[0].'.*?'.$min[0].'/', $text, $m, 0, $count)) 1979 | $count += strlen($m[0]) - 1; 1980 | break; 1981 | case '//': 1982 | $skip = strpos($text, "\n", $count); 1983 | if ($skip === false) $skip = strlen($text) - $count; 1984 | else $skip -= $count; 1985 | break; 1986 | case '/*': 1987 | if (preg_match('/\/\*.*?\*\//s', $text, $m, 0, $count)) { 1988 | $skip = strlen($m[0]); 1989 | $newlines = substr_count($m[0], "\n"); 1990 | } 1991 | break; 1992 | } 1993 | 1994 | if ($skip == 0) $count += strlen($min[0]); 1995 | 1996 | $out .= substr($text, 0, $count).str_repeat("\n", $newlines); 1997 | $text = substr($text, $count + $skip); 1998 | 1999 | $min = null; 2000 | } 2001 | 2002 | return $out.$text; 2003 | } 2004 | 2005 | public function allParsedFiles() { return $this->allParsedFiles; } 2006 | protected function addParsedFile($file) { 2007 | $this->allParsedFiles[realpath($file)] = filemtime($file); 2008 | } 2009 | 2010 | 2011 | // compile to $in to $out if $in is newer than $out 2012 | // returns true when it compiles, false otherwise 2013 | public static function ccompile($in, $out) { 2014 | if (!is_file($out) || filemtime($in) > filemtime($out)) { 2015 | $less = new lessc($in); 2016 | file_put_contents($out, $less->parse()); 2017 | return true; 2018 | } 2019 | 2020 | return false; 2021 | } 2022 | 2023 | /** 2024 | * Execute lessphp on a .less file or a lessphp cache structure 2025 | * 2026 | * The lessphp cache structure contains information about a specific 2027 | * less file having been parsed. It can be used as a hint for future 2028 | * calls to determine whether or not a rebuild is required. 2029 | * 2030 | * The cache structure contains two important keys that may be used 2031 | * externally: 2032 | * 2033 | * compiled: The final compiled CSS 2034 | * updated: The time (in seconds) the CSS was last compiled 2035 | * 2036 | * The cache structure is a plain-ol' PHP associative array and can 2037 | * be serialized and unserialized without a hitch. 2038 | * 2039 | * @param mixed $in Input 2040 | * @param bool $force Force rebuild? 2041 | * @return array lessphp cache structure 2042 | */ 2043 | public static function cexecute($in, $force = false) { 2044 | 2045 | // assume no root 2046 | $root = null; 2047 | 2048 | if (is_string($in)) { 2049 | $root = $in; 2050 | } elseif (is_array($in) and isset($in['root'])) { 2051 | if ($force or ! isset($in['files'])) { 2052 | // If we are forcing a recompile or if for some reason the 2053 | // structure does not contain any file information we should 2054 | // specify the root to trigger a rebuild. 2055 | $root = $in['root']; 2056 | } elseif (isset($in['files']) and is_array($in['files'])) { 2057 | foreach ($in['files'] as $fname => $ftime ) { 2058 | if (!file_exists($fname) or filemtime($fname) > $ftime) { 2059 | // One of the files we knew about previously has changed 2060 | // so we should look at our incoming root again. 2061 | $root = $in['root']; 2062 | break; 2063 | } 2064 | } 2065 | } 2066 | } else { 2067 | // TODO: Throw an exception? We got neither a string nor something 2068 | // that looks like a compatible lessphp cache structure. 2069 | return null; 2070 | } 2071 | 2072 | if ($root !== null) { 2073 | // If we have a root value which means we should rebuild. 2074 | $less = new lessc($root); 2075 | $out = array(); 2076 | $out['root'] = $root; 2077 | $out['compiled'] = $less->parse(); 2078 | $out['files'] = $less->allParsedFiles(); 2079 | $out['updated'] = time(); 2080 | return $out; 2081 | } else { 2082 | // No changes, pass back the structure 2083 | // we were given initially. 2084 | return $in; 2085 | } 2086 | 2087 | } 2088 | 2089 | static protected $cssColors = array( 2090 | 'aliceblue' => '240,248,255', 2091 | 'antiquewhite' => '250,235,215', 2092 | 'aqua' => '0,255,255', 2093 | 'aquamarine' => '127,255,212', 2094 | 'azure' => '240,255,255', 2095 | 'beige' => '245,245,220', 2096 | 'bisque' => '255,228,196', 2097 | 'black' => '0,0,0', 2098 | 'blanchedalmond' => '255,235,205', 2099 | 'blue' => '0,0,255', 2100 | 'blueviolet' => '138,43,226', 2101 | 'brown' => '165,42,42', 2102 | 'burlywood' => '222,184,135', 2103 | 'cadetblue' => '95,158,160', 2104 | 'chartreuse' => '127,255,0', 2105 | 'chocolate' => '210,105,30', 2106 | 'coral' => '255,127,80', 2107 | 'cornflowerblue' => '100,149,237', 2108 | 'cornsilk' => '255,248,220', 2109 | 'crimson' => '220,20,60', 2110 | 'cyan' => '0,255,255', 2111 | 'darkblue' => '0,0,139', 2112 | 'darkcyan' => '0,139,139', 2113 | 'darkgoldenrod' => '184,134,11', 2114 | 'darkgray' => '169,169,169', 2115 | 'darkgreen' => '0,100,0', 2116 | 'darkgrey' => '169,169,169', 2117 | 'darkkhaki' => '189,183,107', 2118 | 'darkmagenta' => '139,0,139', 2119 | 'darkolivegreen' => '85,107,47', 2120 | 'darkorange' => '255,140,0', 2121 | 'darkorchid' => '153,50,204', 2122 | 'darkred' => '139,0,0', 2123 | 'darksalmon' => '233,150,122', 2124 | 'darkseagreen' => '143,188,143', 2125 | 'darkslateblue' => '72,61,139', 2126 | 'darkslategray' => '47,79,79', 2127 | 'darkslategrey' => '47,79,79', 2128 | 'darkturquoise' => '0,206,209', 2129 | 'darkviolet' => '148,0,211', 2130 | 'deeppink' => '255,20,147', 2131 | 'deepskyblue' => '0,191,255', 2132 | 'dimgray' => '105,105,105', 2133 | 'dimgrey' => '105,105,105', 2134 | 'dodgerblue' => '30,144,255', 2135 | 'firebrick' => '178,34,34', 2136 | 'floralwhite' => '255,250,240', 2137 | 'forestgreen' => '34,139,34', 2138 | 'fuchsia' => '255,0,255', 2139 | 'gainsboro' => '220,220,220', 2140 | 'ghostwhite' => '248,248,255', 2141 | 'gold' => '255,215,0', 2142 | 'goldenrod' => '218,165,32', 2143 | 'gray' => '128,128,128', 2144 | 'green' => '0,128,0', 2145 | 'greenyellow' => '173,255,47', 2146 | 'grey' => '128,128,128', 2147 | 'honeydew' => '240,255,240', 2148 | 'hotpink' => '255,105,180', 2149 | 'indianred' => '205,92,92', 2150 | 'indigo' => '75,0,130', 2151 | 'ivory' => '255,255,240', 2152 | 'khaki' => '240,230,140', 2153 | 'lavender' => '230,230,250', 2154 | 'lavenderblush' => '255,240,245', 2155 | 'lawngreen' => '124,252,0', 2156 | 'lemonchiffon' => '255,250,205', 2157 | 'lightblue' => '173,216,230', 2158 | 'lightcoral' => '240,128,128', 2159 | 'lightcyan' => '224,255,255', 2160 | 'lightgoldenrodyellow' => '250,250,210', 2161 | 'lightgray' => '211,211,211', 2162 | 'lightgreen' => '144,238,144', 2163 | 'lightgrey' => '211,211,211', 2164 | 'lightpink' => '255,182,193', 2165 | 'lightsalmon' => '255,160,122', 2166 | 'lightseagreen' => '32,178,170', 2167 | 'lightskyblue' => '135,206,250', 2168 | 'lightslategray' => '119,136,153', 2169 | 'lightslategrey' => '119,136,153', 2170 | 'lightsteelblue' => '176,196,222', 2171 | 'lightyellow' => '255,255,224', 2172 | 'lime' => '0,255,0', 2173 | 'limegreen' => '50,205,50', 2174 | 'linen' => '250,240,230', 2175 | 'magenta' => '255,0,255', 2176 | 'maroon' => '128,0,0', 2177 | 'mediumaquamarine' => '102,205,170', 2178 | 'mediumblue' => '0,0,205', 2179 | 'mediumorchid' => '186,85,211', 2180 | 'mediumpurple' => '147,112,219', 2181 | 'mediumseagreen' => '60,179,113', 2182 | 'mediumslateblue' => '123,104,238', 2183 | 'mediumspringgreen' => '0,250,154', 2184 | 'mediumturquoise' => '72,209,204', 2185 | 'mediumvioletred' => '199,21,133', 2186 | 'midnightblue' => '25,25,112', 2187 | 'mintcream' => '245,255,250', 2188 | 'mistyrose' => '255,228,225', 2189 | 'moccasin' => '255,228,181', 2190 | 'navajowhite' => '255,222,173', 2191 | 'navy' => '0,0,128', 2192 | 'oldlace' => '253,245,230', 2193 | 'olive' => '128,128,0', 2194 | 'olivedrab' => '107,142,35', 2195 | 'orange' => '255,165,0', 2196 | 'orangered' => '255,69,0', 2197 | 'orchid' => '218,112,214', 2198 | 'palegoldenrod' => '238,232,170', 2199 | 'palegreen' => '152,251,152', 2200 | 'paleturquoise' => '175,238,238', 2201 | 'palevioletred' => '219,112,147', 2202 | 'papayawhip' => '255,239,213', 2203 | 'peachpuff' => '255,218,185', 2204 | 'peru' => '205,133,63', 2205 | 'pink' => '255,192,203', 2206 | 'plum' => '221,160,221', 2207 | 'powderblue' => '176,224,230', 2208 | 'purple' => '128,0,128', 2209 | 'red' => '255,0,0', 2210 | 'rosybrown' => '188,143,143', 2211 | 'royalblue' => '65,105,225', 2212 | 'saddlebrown' => '139,69,19', 2213 | 'salmon' => '250,128,114', 2214 | 'sandybrown' => '244,164,96', 2215 | 'seagreen' => '46,139,87', 2216 | 'seashell' => '255,245,238', 2217 | 'sienna' => '160,82,45', 2218 | 'silver' => '192,192,192', 2219 | 'skyblue' => '135,206,235', 2220 | 'slateblue' => '106,90,205', 2221 | 'slategray' => '112,128,144', 2222 | 'slategrey' => '112,128,144', 2223 | 'snow' => '255,250,250', 2224 | 'springgreen' => '0,255,127', 2225 | 'steelblue' => '70,130,180', 2226 | 'tan' => '210,180,140', 2227 | 'teal' => '0,128,128', 2228 | 'thistle' => '216,191,216', 2229 | 'tomato' => '255,99,71', 2230 | 'turquoise' => '64,224,208', 2231 | 'violet' => '238,130,238', 2232 | 'wheat' => '245,222,179', 2233 | 'white' => '255,255,255', 2234 | 'whitesmoke' => '245,245,245', 2235 | 'yellow' => '255,255,0', 2236 | 'yellowgreen' => '154,205,50' 2237 | ); 2238 | } 2239 | 2240 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | parse($lessString); 37 | exit; 38 | } 39 | catch (Exception $e) { 40 | $error = $e->getMessage(); 41 | } 42 | } 43 | ?> 44 | 45 | 46 | 47 | 48 | 49 | Twitter Bootstrap Generator 50 | 51 | 66 | 76 | 77 | 78 |
79 |
80 |
81 | Bootstrap Generator 82 |
83 |
84 |
85 |
86 |
87 | × 88 |

Just a note: the Bootstrap Generator is for use with version 1.4 only. Twitter introduced a customized download facility with version 2.0 of Bootstrap, which you can find at http://twitter.github.com/bootstrap/customize.html.

89 |
90 | 91 |
92 | × 93 |

Oh, snap! An error occurred. Sorry about that! Please try again later.

94 |
95 | 96 |
97 |

Bootstrap Generator

98 |

Kick-start your Twitter Bootstrap project the way you want. Simply alter the options below and click "Generate" to get your compiled Bootstrap CSS file.

99 |
100 | 103 |
104 |
105 | Links 106 |
107 | 108 |
109 | 110 | Usually a hex. code, i.e. #0069d6 111 |
112 |
113 |
114 | 115 |
116 | 117 | Either a hex. code or a LESS function, i.e. darken(@linkColor, 15) 118 |
119 |
120 |
121 |
122 | Grays 123 |
124 | 125 |
126 | 127 |
128 |
129 |
130 | 131 |
132 | 133 |
134 |
135 |
136 |
137 | Accent Colors 138 |
139 | 140 |
141 | 142 |
143 |
144 |
145 | 146 |
147 | 148 |
149 |
150 |
151 | 152 |
153 | 154 |
155 |
156 |
157 | 158 |
159 | 160 |
161 |
162 |
163 | 164 |
165 | 166 |
167 |
168 |
169 | 170 |
171 | 172 |
173 |
174 |
175 | 176 |
177 | 178 |
179 |
180 |
181 | 182 |
183 | 184 |
185 |
186 |
187 |
188 | Baseline Grid 189 |
190 | 191 |
192 | 193 | px 194 |
195 |
196 |
197 | 198 |
199 | 200 | px 201 |
202 |
203 |
204 |
205 | Grid 206 |
207 | 208 |
209 | 210 |
211 |
212 |
213 | 214 |
215 | 216 | px 217 |
218 |
219 |
220 | 221 |
222 | 223 | px 224 |
225 |
226 |
227 |
228 | Font Families 229 |
230 | 231 |
232 | 233 | No ending semi-colon is required. 234 |
235 |
236 |
237 | 238 |
239 | 240 | No ending semi-colon is required. 241 |
242 |
243 |
244 | 245 |
246 | 247 | No ending semi-colon is required. 248 |
249 |
250 |
251 |
252 | 253 |
254 |
255 | 258 |
259 | Fork me on GitHub 260 | 261 | 275 | 276 | 277 | -------------------------------------------------------------------------------- /lib/bootstrap.less: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap @VERSION 3 | * 4 | * Copyright 2011 Twitter, Inc 5 | * Licensed under the Apache License v2.0 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Designed and built with all the love in the world @twitter by @mdo and @fat. 9 | * Date: @DATE 10 | */ 11 | 12 | // CSS Reset 13 | @import "reset.less"; 14 | 15 | // Core variables and mixins 16 | @import "variables.less"; // Modify this for custom colors, font-sizes, etc 17 | @import "mixins.less"; 18 | 19 | // Grid system and page structure 20 | @import "scaffolding.less"; 21 | 22 | // Styled patterns and elements 23 | @import "type.less"; 24 | @import "forms.less"; 25 | @import "tables.less"; 26 | @import "patterns.less"; -------------------------------------------------------------------------------- /lib/forms.less: -------------------------------------------------------------------------------- 1 | /* Forms.less 2 | * Base styles for various input types, form layouts, and states 3 | * ------------------------------------------------------------- */ 4 | 5 | 6 | // FORM STYLES 7 | // ----------- 8 | 9 | form { 10 | margin-bottom: @baseline; 11 | } 12 | 13 | // Groups of fields with labels on top (legends) 14 | fieldset { 15 | margin-bottom: @baseline; 16 | padding-top: @baseline; 17 | legend { 18 | display: block; 19 | padding-left: 150px; 20 | font-size: @basefont * 1.5; 21 | line-height: 1; 22 | color: @grayDark; 23 | *padding: 0 0 5px 145px; /* IE6-7 */ 24 | *line-height: 1.5; /* IE6-7 */ 25 | } 26 | } 27 | 28 | // Parent element that clears floats and wraps labels and fields together 29 | form .clearfix { 30 | margin-bottom: @baseline; 31 | .clearfix() 32 | } 33 | 34 | // Set font for forms 35 | label, 36 | input, 37 | select, 38 | textarea { 39 | #font > .sans-serif(normal,13px,normal); 40 | } 41 | 42 | // Float labels left 43 | label { 44 | padding-top: 6px; 45 | font-size: @basefont; 46 | line-height: @baseline; 47 | float: left; 48 | width: 130px; 49 | text-align: right; 50 | color: @grayDark; 51 | } 52 | 53 | // Shift over the inside div to align all label's relevant content 54 | form .input { 55 | margin-left: 150px; 56 | } 57 | 58 | // Checkboxs and radio buttons 59 | input[type=checkbox], 60 | input[type=radio] { 61 | cursor: pointer; 62 | } 63 | 64 | // Inputs, Textareas, Selects 65 | input, 66 | textarea, 67 | select, 68 | .uneditable-input { 69 | display: inline-block; 70 | width: 210px; 71 | height: @baseline; 72 | padding: 4px; 73 | font-size: @basefont; 74 | line-height: @baseline; 75 | color: @gray; 76 | border: 1px solid #ccc; 77 | .border-radius(3px); 78 | } 79 | 80 | // remove padding from select 81 | select { 82 | padding: initial; 83 | } 84 | 85 | // mini reset for non-html5 file types 86 | input[type=checkbox], 87 | input[type=radio] { 88 | width: auto; 89 | height: auto; 90 | padding: 0; 91 | margin: 3px 0; 92 | *margin-top: 0; /* IE6-7 */ 93 | line-height: normal; 94 | border: none; 95 | } 96 | 97 | input[type=file] { 98 | background-color: @white; 99 | padding: initial; 100 | border: initial; 101 | line-height: initial; 102 | .box-shadow(none); 103 | } 104 | 105 | input[type=button], 106 | input[type=reset], 107 | input[type=submit] { 108 | width: auto; 109 | height: auto; 110 | } 111 | 112 | select, 113 | input[type=file] { 114 | height: @baseline * 1.5; // In IE7, the height of the select element cannot be changed by height, only font-size 115 | *height: auto; // Reset for IE7 116 | line-height: @baseline * 1.5; 117 | *margin-top: 4px; /* For IE7, add top margin to align select with labels */ 118 | } 119 | 120 | // Make multiple select elements height not fixed 121 | select[multiple] { 122 | height: inherit; 123 | background-color: @white; // Fixes Chromium bug of unreadable items 124 | } 125 | 126 | textarea { 127 | height: auto; 128 | } 129 | 130 | // For text that needs to appear as an input but should not be an input 131 | .uneditable-input { 132 | background-color: @white; 133 | display: block; 134 | border-color: #eee; 135 | .box-shadow(inset 0 1px 2px rgba(0,0,0,.025)); 136 | cursor: not-allowed; 137 | } 138 | 139 | // Placeholder text gets special styles; can't be bundled together though for some reason 140 | :-moz-placeholder { 141 | color: @grayLight; 142 | } 143 | ::-webkit-input-placeholder { 144 | color: @grayLight; 145 | } 146 | 147 | // Focus states 148 | input, 149 | textarea { 150 | @transition: border linear .2s, box-shadow linear .2s; 151 | .transition(@transition); 152 | .box-shadow(inset 0 1px 3px rgba(0,0,0,.1)); 153 | } 154 | input:focus, 155 | textarea:focus { 156 | outline: 0; 157 | border-color: rgba(82,168,236,.8); 158 | @shadow: inset 0 1px 3px rgba(0,0,0,.1), 0 0 8px rgba(82,168,236,.6); 159 | .box-shadow(@shadow); 160 | } 161 | input[type=file]:focus, 162 | input[type=checkbox]:focus, 163 | select:focus { 164 | .box-shadow(none); // override for file inputs 165 | outline: 1px dotted #666; // Selet elements don't get box-shadow styles, so instead we do outline 166 | } 167 | 168 | 169 | // FORM FIELD FEEDBACK STATES 170 | // -------------------------- 171 | 172 | // Mixin for form field states 173 | .formFieldState(@textColor: #555, @borderColor: #ccc, @backgroundColor: #f5f5f5) { 174 | // Set the text color 175 | > label, 176 | .help-block, 177 | .help-inline { 178 | color: @textColor; 179 | } 180 | // Style inputs accordingly 181 | input, 182 | textarea { 183 | color: @textColor; 184 | border-color: @borderColor; 185 | &:focus { 186 | border-color: darken(@borderColor, 10%); 187 | .box-shadow(0 0 6px lighten(@borderColor, 20%)); 188 | } 189 | } 190 | // Give a small background color for input-prepend/-append 191 | .input-prepend .add-on, 192 | .input-append .add-on { 193 | color: @textColor; 194 | background-color: @backgroundColor; 195 | border-color: @textColor; 196 | } 197 | } 198 | // Error 199 | form .clearfix.error { 200 | .formFieldState(#b94a48, #ee5f5b, lighten(#ee5f5b, 30%)); 201 | } 202 | // Warning 203 | form .clearfix.warning { 204 | .formFieldState(#c09853, #ccae64, lighten(#CCAE64, 5%)); 205 | } 206 | // Success 207 | form .clearfix.success { 208 | .formFieldState(#468847, #57a957, lighten(#57a957, 30%)); 209 | } 210 | 211 | 212 | // Form element sizes 213 | // TODO v2: remove duplication here and just stick to .input-[size] in light of adding .spanN sizes 214 | .input-mini, 215 | input.mini, 216 | textarea.mini, 217 | select.mini { 218 | width: 60px; 219 | } 220 | .input-small, 221 | input.small, 222 | textarea.small, 223 | select.small { 224 | width: 90px; 225 | } 226 | .input-medium, 227 | input.medium, 228 | textarea.medium, 229 | select.medium { 230 | width: 150px; 231 | } 232 | .input-large, 233 | input.large, 234 | textarea.large, 235 | select.large { 236 | width: 210px; 237 | } 238 | .input-xlarge, 239 | input.xlarge, 240 | textarea.xlarge, 241 | select.xlarge { 242 | width: 270px; 243 | } 244 | .input-xxlarge, 245 | input.xxlarge, 246 | textarea.xxlarge, 247 | select.xxlarge { 248 | width: 530px; 249 | } 250 | textarea.xxlarge { 251 | overflow-y: auto; 252 | } 253 | 254 | // Grid style input sizes 255 | // This is a duplication of the main grid .columns() mixin, but subtracts 10px to account for input padding and border 256 | .formColumns(@columnSpan: 1) { 257 | display: inline-block; 258 | float: none; 259 | width: ((@gridColumnWidth) * @columnSpan) + (@gridGutterWidth * (@columnSpan - 1)) - 10; 260 | margin-left: 0; 261 | } 262 | input, 263 | textarea { 264 | // Default columns 265 | &.span1 { .formColumns(1); } 266 | &.span2 { .formColumns(2); } 267 | &.span3 { .formColumns(3); } 268 | &.span4 { .formColumns(4); } 269 | &.span5 { .formColumns(5); } 270 | &.span6 { .formColumns(6); } 271 | &.span7 { .formColumns(7); } 272 | &.span8 { .formColumns(8); } 273 | &.span9 { .formColumns(9); } 274 | &.span10 { .formColumns(10); } 275 | &.span11 { .formColumns(11); } 276 | &.span12 { .formColumns(12); } 277 | &.span13 { .formColumns(13); } 278 | &.span14 { .formColumns(14); } 279 | &.span15 { .formColumns(15); } 280 | &.span16 { .formColumns(16); } 281 | } 282 | 283 | // Disabled and read-only inputs 284 | input[disabled], 285 | select[disabled], 286 | textarea[disabled], 287 | input[readonly], 288 | select[readonly], 289 | textarea[readonly] { 290 | background-color: #f5f5f5; 291 | border-color: #ddd; 292 | cursor: not-allowed; 293 | } 294 | 295 | // Actions (the buttons) 296 | .actions { 297 | background: #f5f5f5; 298 | margin-top: @baseline; 299 | margin-bottom: @baseline; 300 | padding: (@baseline - 1) 20px @baseline 150px; 301 | border-top: 1px solid #ddd; 302 | .border-radius(0 0 3px 3px); 303 | .secondary-action { 304 | float: right; 305 | a { 306 | line-height: 30px; 307 | &:hover { 308 | text-decoration: underline; 309 | } 310 | } 311 | } 312 | } 313 | 314 | // Help Text 315 | // TODO: Do we need to set basefont and baseline here? 316 | .help-inline, 317 | .help-block { 318 | font-size: @basefont; 319 | line-height: @baseline; 320 | color: @grayLight; 321 | } 322 | .help-inline { 323 | padding-left: 5px; 324 | *position: relative; /* IE6-7 */ 325 | *top: -5px; /* IE6-7 */ 326 | } 327 | 328 | // Big blocks of help text 329 | .help-block { 330 | display: block; 331 | max-width: 600px; 332 | } 333 | 334 | // Inline Fields (input fields that appear as inline objects 335 | .inline-inputs { 336 | color: @gray; 337 | span { 338 | padding: 0 2px 0 1px; 339 | } 340 | } 341 | 342 | // Allow us to put symbols and text within the input field for a cleaner look 343 | .input-prepend, 344 | .input-append { 345 | input { 346 | .border-radius(0 3px 3px 0); 347 | } 348 | .add-on { 349 | position: relative; 350 | background: #f5f5f5; 351 | border: 1px solid #ccc; 352 | z-index: 2; 353 | float: left; 354 | display: block; 355 | width: auto; 356 | min-width: 16px; 357 | height: 18px; 358 | padding: 4px 4px 4px 5px; 359 | margin-right: -1px; 360 | font-weight: normal; 361 | line-height: 18px; 362 | color: @grayLight; 363 | text-align: center; 364 | text-shadow: 0 1px 0 @white; 365 | .border-radius(3px 0 0 3px); 366 | } 367 | .active { 368 | background: lighten(@green, 30); 369 | border-color: @green; 370 | } 371 | } 372 | .input-prepend { 373 | .add-on { 374 | *margin-top: 1px; /* IE6-7 */ 375 | } 376 | } 377 | .input-append { 378 | input { 379 | float: left; 380 | .border-radius(3px 0 0 3px); 381 | } 382 | .add-on { 383 | .border-radius(0 3px 3px 0); 384 | margin-right: 0; 385 | margin-left: -1px; 386 | } 387 | } 388 | 389 | // Stacked options for forms (radio buttons or checkboxes) 390 | .inputs-list { 391 | margin: 0 0 5px; 392 | width: 100%; 393 | li { 394 | display: block; 395 | padding: 0; 396 | width: 100%; 397 | } 398 | label { 399 | display: block; 400 | float: none; 401 | width: auto; 402 | padding: 0; 403 | margin-left: 20px; 404 | line-height: @baseline; 405 | text-align: left; 406 | white-space: normal; 407 | strong { 408 | color: @gray; 409 | } 410 | small { 411 | font-size: @basefont - 2; 412 | font-weight: normal; 413 | } 414 | } 415 | .inputs-list { 416 | margin-left: 25px; 417 | margin-bottom: 10px; 418 | padding-top: 0; 419 | } 420 | &:first-child { 421 | padding-top: 6px; 422 | } 423 | li + li { 424 | padding-top: 2px; 425 | } 426 | input[type=radio], 427 | input[type=checkbox] { 428 | margin-bottom: 0; 429 | margin-left: -20px; 430 | float: left; 431 | } 432 | } 433 | 434 | // Stacked forms 435 | .form-stacked { 436 | padding-left: 20px; 437 | fieldset { 438 | padding-top: @baseline / 2; 439 | } 440 | legend { 441 | padding-left: 0; 442 | } 443 | label { 444 | display: block; 445 | float: none; 446 | width: auto; 447 | font-weight: bold; 448 | text-align: left; 449 | line-height: 20px; 450 | padding-top: 0; 451 | } 452 | .clearfix { 453 | margin-bottom: @baseline / 2; 454 | div.input { 455 | margin-left: 0; 456 | } 457 | } 458 | .inputs-list { 459 | margin-bottom: 0; 460 | li { 461 | padding-top: 0; 462 | label { 463 | font-weight: normal; 464 | padding-top: 0; 465 | } 466 | } 467 | } 468 | div.clearfix.error { 469 | padding-top: 10px; 470 | padding-bottom: 10px; 471 | padding-left: 10px; 472 | margin-top: 0; 473 | margin-left: -10px; 474 | } 475 | .actions { 476 | margin-left: -20px; 477 | padding-left: 20px; 478 | } 479 | } 480 | -------------------------------------------------------------------------------- /lib/mixins.less: -------------------------------------------------------------------------------- 1 | /* Mixins.less 2 | * Snippets of reusable CSS to develop faster and keep code readable 3 | * ----------------------------------------------------------------- */ 4 | 5 | 6 | // Clearfix for clearing floats like a boss h5bp.com/q 7 | .clearfix() { 8 | zoom: 1; 9 | &:before, 10 | &:after { 11 | display: table; 12 | content: ""; 13 | zoom: 1; 14 | } 15 | &:after { 16 | clear: both; 17 | } 18 | } 19 | 20 | // Center-align a block level element 21 | .center-block() { 22 | display: block; 23 | margin-left: auto; 24 | margin-right: auto; 25 | } 26 | 27 | // Sizing shortcuts 28 | .size(@height: 5px, @width: 5px) { 29 | height: @height; 30 | width: @width; 31 | } 32 | .square(@size: 5px) { 33 | .size(@size, @size); 34 | } 35 | 36 | // Input placeholder text 37 | .placeholder(@color: @grayLight) { 38 | :-moz-placeholder { 39 | color: @color; 40 | } 41 | ::-webkit-input-placeholder { 42 | color: @color; 43 | } 44 | } 45 | 46 | // Font Stacks 47 | #font { 48 | .shorthand(@weight: normal, @size: 14px, @lineHeight: 20px) { 49 | font-size: @size; 50 | font-weight: @weight; 51 | line-height: @lineHeight; 52 | } 53 | .sans-serif(@weight: normal, @size: 14px, @lineHeight: 20px) { 54 | font-family: %sansSerifFontFamily%; 55 | font-size: @size; 56 | font-weight: @weight; 57 | line-height: @lineHeight; 58 | } 59 | .serif(@weight: normal, @size: 14px, @lineHeight: 20px) { 60 | font-family: %serifFontFamily%; 61 | font-size: @size; 62 | font-weight: @weight; 63 | line-height: @lineHeight; 64 | } 65 | .monospace(@weight: normal, @size: 12px, @lineHeight: 20px) { 66 | font-family: %monospaceFontFamily%; 67 | font-size: @size; 68 | font-weight: @weight; 69 | line-height: @lineHeight; 70 | } 71 | } 72 | 73 | // Grid System 74 | .fixed-container() { 75 | width: @siteWidth; 76 | margin-left: auto; 77 | margin-right: auto; 78 | .clearfix(); 79 | } 80 | .columns(@columnSpan: 1) { 81 | width: (@gridColumnWidth * @columnSpan) + (@gridGutterWidth * (@columnSpan - 1)); 82 | } 83 | .offset(@columnOffset: 1) { 84 | margin-left: (@gridColumnWidth * @columnOffset) + (@gridGutterWidth * (@columnOffset - 1)) + @extraSpace; 85 | } 86 | // Necessary grid styles for every column to make them appear next to each other horizontally 87 | .gridColumn() { 88 | display: inline; 89 | float: left; 90 | margin-left: @gridGutterWidth; 91 | } 92 | // makeColumn can be used to mark any element (e.g., .content-primary) as a column without changing markup to .span something 93 | .makeColumn(@columnSpan: 1) { 94 | .gridColumn(); 95 | .columns(@columnSpan); 96 | } 97 | 98 | // Border Radius 99 | .border-radius(@radius: 5px) { 100 | -webkit-border-radius: @radius; 101 | -moz-border-radius: @radius; 102 | border-radius: @radius; 103 | } 104 | 105 | // Drop shadows 106 | .box-shadow(@shadow: 0 1px 3px rgba(0,0,0,.25)) { 107 | -webkit-box-shadow: @shadow; 108 | -moz-box-shadow: @shadow; 109 | box-shadow: @shadow; 110 | } 111 | 112 | // Transitions 113 | .transition(@transition) { 114 | -webkit-transition: @transition; 115 | -moz-transition: @transition; 116 | -ms-transition: @transition; 117 | -o-transition: @transition; 118 | transition: @transition; 119 | } 120 | 121 | // Background clipping 122 | .background-clip(@clip) { 123 | -webkit-background-clip: @clip; 124 | -moz-background-clip: @clip; 125 | background-clip: @clip; 126 | } 127 | 128 | // CSS3 Content Columns 129 | .content-columns(@columnCount, @columnGap: 20px) { 130 | -webkit-column-count: @columnCount; 131 | -moz-column-count: @columnCount; 132 | column-count: @columnCount; 133 | -webkit-column-gap: @columnGap; 134 | -moz-column-gap: @columnGap; 135 | column-gap: @columnGap; 136 | } 137 | 138 | // Make any element resizable for prototyping 139 | .resizable(@direction: both) { 140 | resize: @direction; // Options are horizontal, vertical, both 141 | overflow: auto; // Safari fix 142 | } 143 | 144 | // Add an alphatransparency value to any background or border color (via Elyse Holladay) 145 | #translucent { 146 | .background(@color: @white, @alpha: 1) { 147 | background-color: hsla(hue(@color), saturation(@color), lightness(@color), @alpha); 148 | } 149 | .border(@color: @white, @alpha: 1) { 150 | border-color: hsla(hue(@color), saturation(@color), lightness(@color), @alpha); 151 | background-clip: padding-box; 152 | } 153 | } 154 | 155 | // Gradient Bar Colors for buttons and allerts 156 | .gradientBar(@primaryColor, @secondaryColor) { 157 | #gradient > .vertical(@primaryColor, @secondaryColor); 158 | text-shadow: 0 -1px 0 rgba(0,0,0,.25); 159 | border-color: @secondaryColor @secondaryColor darken(@secondaryColor, 15%); 160 | border-color: rgba(0,0,0,.1) rgba(0,0,0,.1) fadein(rgba(0,0,0,.1), 15%); 161 | } 162 | 163 | // Gradients 164 | #gradient { 165 | .horizontal (@startColor: #555, @endColor: #333) { 166 | background-color: @endColor; 167 | background-repeat: repeat-x; 168 | background-image: -khtml-gradient(linear, left top, right top, from(@startColor), to(@endColor)); // Konqueror 169 | background-image: -moz-linear-gradient(left, @startColor, @endColor); // FF 3.6+ 170 | background-image: -ms-linear-gradient(left, @startColor, @endColor); // IE10 171 | background-image: -webkit-gradient(linear, left top, right top, color-stop(0%, @startColor), color-stop(100%, @endColor)); // Safari 4+, Chrome 2+ 172 | background-image: -webkit-linear-gradient(left, @startColor, @endColor); // Safari 5.1+, Chrome 10+ 173 | background-image: -o-linear-gradient(left, @startColor, @endColor); // Opera 11.10 174 | background-image: linear-gradient(left, @startColor, @endColor); // Le standard 175 | filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)",@startColor,@endColor)); // IE9 and down 176 | } 177 | .vertical (@startColor: #555, @endColor: #333) { 178 | background-color: @endColor; 179 | background-repeat: repeat-x; 180 | background-image: -khtml-gradient(linear, left top, left bottom, from(@startColor), to(@endColor)); // Konqueror 181 | background-image: -moz-linear-gradient(top, @startColor, @endColor); // FF 3.6+ 182 | background-image: -ms-linear-gradient(top, @startColor, @endColor); // IE10 183 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, @startColor), color-stop(100%, @endColor)); // Safari 4+, Chrome 2+ 184 | background-image: -webkit-linear-gradient(top, @startColor, @endColor); // Safari 5.1+, Chrome 10+ 185 | background-image: -o-linear-gradient(top, @startColor, @endColor); // Opera 11.10 186 | background-image: linear-gradient(top, @startColor, @endColor); // The standard 187 | filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",@startColor,@endColor)); // IE9 and down 188 | } 189 | .directional (@startColor: #555, @endColor: #333, @deg: 45deg) { 190 | background-color: @endColor; 191 | background-repeat: repeat-x; 192 | background-image: -moz-linear-gradient(@deg, @startColor, @endColor); // FF 3.6+ 193 | background-image: -ms-linear-gradient(@deg, @startColor, @endColor); // IE10 194 | background-image: -webkit-linear-gradient(@deg, @startColor, @endColor); // Safari 5.1+, Chrome 10+ 195 | background-image: -o-linear-gradient(@deg, @startColor, @endColor); // Opera 11.10 196 | background-image: linear-gradient(@deg, @startColor, @endColor); // The standard 197 | } 198 | .vertical-three-colors(@startColor: #00b3ee, @midColor: #7a43b6, @colorStop: 50%, @endColor: #c3325f) { 199 | background-color: @endColor; 200 | background-repeat: no-repeat; 201 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(@startColor), color-stop(@colorStop, @midColor), to(@endColor)); 202 | background-image: -webkit-linear-gradient(@startColor, @midColor @colorStop, @endColor); 203 | background-image: -moz-linear-gradient(top, @startColor, @midColor @colorStop, @endColor); 204 | background-image: -ms-linear-gradient(@startColor, @midColor @colorStop, @endColor); 205 | background-image: -o-linear-gradient(@startColor, @midColor @colorStop, @endColor); 206 | background-image: linear-gradient(@startColor, @midColor @colorStop, @endColor); 207 | 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 208 | } 209 | } 210 | 211 | // Reset filters for IE 212 | .reset-filter() { 213 | filter: e(%("progid:DXImageTransform.Microsoft.gradient(enabled = false)")); 214 | } 215 | 216 | // Opacity 217 | .opacity(@opacity: 100) { 218 | filter: e(%("alpha(opacity=%d)", @opacity)); 219 | -khtml-opacity: @opacity / 100; 220 | -moz-opacity: @opacity / 100; 221 | opacity: @opacity / 100; 222 | } -------------------------------------------------------------------------------- /lib/patterns.less: -------------------------------------------------------------------------------- 1 | /* Patterns.less 2 | * Repeatable UI elements outside the base styles provided from the scaffolding 3 | * ---------------------------------------------------------------------------- */ 4 | 5 | 6 | // TOPBAR 7 | // ------ 8 | 9 | // Topbar for Branding and Nav 10 | .topbar { 11 | height: 40px; 12 | position: fixed; 13 | top: 0; 14 | left: 0; 15 | right: 0; 16 | z-index: 10000; 17 | overflow: visible; 18 | 19 | // Links get text shadow 20 | a { 21 | color: @grayLight; 22 | text-shadow: 0 -1px 0 rgba(0,0,0,.25); 23 | } 24 | 25 | // Hover and active states 26 | // h3 for backwards compatibility 27 | h3 a:hover, 28 | .brand:hover, 29 | ul .active > a { 30 | background-color: #333; 31 | background-color: rgba(255,255,255,.05); 32 | color: @white; 33 | text-decoration: none; 34 | } 35 | 36 | // Website name 37 | // h3 left for backwards compatibility 38 | h3 { 39 | position: relative; 40 | } 41 | h3 a, 42 | .brand { 43 | float: left; 44 | display: block; 45 | padding: 8px 20px 12px; 46 | margin-left: -20px; // negative indent to left-align the text down the page 47 | color: @white; 48 | font-size: 20px; 49 | font-weight: 200; 50 | line-height: 1; 51 | } 52 | 53 | // Plain text in topbar 54 | p { 55 | margin: 0; 56 | line-height: 40px; 57 | a:hover { 58 | background-color: transparent; 59 | color: @white; 60 | } 61 | } 62 | 63 | // Search Form 64 | form { 65 | float: left; 66 | margin: 5px 0 0 0; 67 | position: relative; 68 | .opacity(100); 69 | } 70 | // Todo: remove from v2.0 when ready, added for legacy 71 | form.pull-right { 72 | float: right; 73 | } 74 | input { 75 | background-color: #444; 76 | background-color: rgba(255,255,255,.3); 77 | #font > .sans-serif(13px, normal, 1); 78 | padding: 4px 9px; 79 | color: @white; 80 | color: rgba(255,255,255,.75); 81 | border: 1px solid #111; 82 | .border-radius(4px); 83 | @shadow: inset 0 1px 2px rgba(0,0,0,.1), 0 1px 0px rgba(255,255,255,.25); 84 | .box-shadow(@shadow); 85 | .transition(none); 86 | 87 | // Placeholder text gets special styles; can't be bundled together though for some reason 88 | &:-moz-placeholder { 89 | color: @grayLighter; 90 | } 91 | &::-webkit-input-placeholder { 92 | color: @grayLighter; 93 | } 94 | // Hover states 95 | &:hover { 96 | background-color: @grayLight; 97 | background-color: rgba(255,255,255,.5); 98 | color: @white; 99 | } 100 | // Focus states (we use .focused since IE8 and down doesn't support :focus) 101 | &:focus, 102 | &.focused { 103 | outline: 0; 104 | background-color: @white; 105 | color: @grayDark; 106 | text-shadow: 0 1px 0 @white; 107 | border: 0; 108 | padding: 5px 10px; 109 | .box-shadow(0 0 3px rgba(0,0,0,.15)); 110 | } 111 | } 112 | } 113 | 114 | // gradient is applied to it's own element because overflow visible is not honored by ie when filter is present 115 | // For backwards compatibility, include .topbar .fill 116 | .topbar-inner, 117 | .topbar .fill { 118 | background-color: #222; 119 | #gradient > .vertical(#333, #222); 120 | @shadow: 0 1px 3px rgba(0,0,0,.25), inset 0 -1px 0 rgba(0,0,0,.1); 121 | .box-shadow(@shadow); 122 | } 123 | 124 | 125 | // NAVIGATION 126 | // ---------- 127 | 128 | // Topbar Nav 129 | // ul.nav for all topbar based navigation to avoid inheritance issues and over-specificity 130 | // For backwards compatibility, leave in .topbar div > ul 131 | .topbar div > ul, 132 | .nav { 133 | display: block; 134 | float: left; 135 | margin: 0 10px 0 0; 136 | position: relative; 137 | left: 0; 138 | > li { 139 | display: block; 140 | float: left; 141 | } 142 | a { 143 | display: block; 144 | float: none; 145 | padding: 10px 10px 11px; 146 | line-height: 19px; 147 | text-decoration: none; 148 | &:hover { 149 | color: @white; 150 | text-decoration: none; 151 | } 152 | } 153 | .active > a { 154 | background-color: #222; 155 | background-color: rgba(0,0,0,.5); 156 | } 157 | 158 | // Secondary (floated right) nav in topbar 159 | &.secondary-nav { 160 | float: right; 161 | margin-left: 10px; 162 | margin-right: 0; 163 | // backwards compatibility 164 | .menu-dropdown, 165 | .dropdown-menu { 166 | right: 0; 167 | border: 0; 168 | } 169 | } 170 | // Dropdowns within the .nav 171 | // a.menu:hover and li.open .menu for backwards compatibility 172 | a.menu:hover, 173 | li.open .menu, 174 | .dropdown-toggle:hover, 175 | .dropdown.open .dropdown-toggle { 176 | background: #444; 177 | background: rgba(255,255,255,.05); 178 | } 179 | // .menu-dropdown for backwards compatibility 180 | .menu-dropdown, 181 | .dropdown-menu { 182 | background-color: #333; 183 | // a.menu for backwards compatibility 184 | a.menu, 185 | .dropdown-toggle { 186 | color: @white; 187 | &.open { 188 | background: #444; 189 | background: rgba(255,255,255,.05); 190 | } 191 | } 192 | li a { 193 | color: #999; 194 | text-shadow: 0 1px 0 rgba(0,0,0,.5); 195 | &:hover { 196 | #gradient > .vertical(#292929,#191919); 197 | color: @white; 198 | } 199 | } 200 | .active a { 201 | color: @white; 202 | } 203 | .divider { 204 | background-color: #222; 205 | border-color: #444; 206 | } 207 | } 208 | } 209 | 210 | // For backwards compatibility with new dropdowns, redeclare dropdown link padding 211 | .topbar ul .menu-dropdown li a, 212 | .topbar ul .dropdown-menu li a { 213 | padding: 4px 15px; 214 | } 215 | 216 | // Dropdown Menus 217 | // Use the .menu class on any
  • element within the topbar or ul.tabs and you'll get some superfancy dropdowns 218 | // li.menu for backwards compatibility 219 | li.menu, 220 | .dropdown { 221 | position: relative; 222 | } 223 | // The link that is clicked to toggle the dropdown 224 | // a.menu for backwards compatibility 225 | a.menu:after, 226 | .dropdown-toggle:after { 227 | width: 0; 228 | height: 0; 229 | display: inline-block; 230 | content: "↓"; 231 | text-indent: -99999px; 232 | vertical-align: top; 233 | margin-top: 8px; 234 | margin-left: 4px; 235 | border-left: 4px solid transparent; 236 | border-right: 4px solid transparent; 237 | border-top: 4px solid @white; 238 | .opacity(50); 239 | } 240 | // The dropdown menu (ul) 241 | // .menu-dropdown for backwards compatibility 242 | .menu-dropdown, 243 | .dropdown-menu { 244 | background-color: @white; 245 | float: left; 246 | display: none; // None by default, but block on "open" of the menu 247 | position: absolute; 248 | top: 40px; 249 | z-index: 900; 250 | min-width: 160px; 251 | max-width: 220px; 252 | _width: 160px; 253 | margin-left: 0; // override default ul styles 254 | margin-right: 0; 255 | padding: 6px 0; 256 | zoom: 1; // do we need this? 257 | border-color: #999; 258 | border-color: rgba(0,0,0,.2); 259 | border-style: solid; 260 | border-width: 0 1px 1px; 261 | .border-radius(0 0 6px 6px); 262 | .box-shadow(0 2px 4px rgba(0,0,0,.2)); 263 | .background-clip(padding-box); 264 | 265 | // Unfloat any li's to make them stack 266 | li { 267 | float: none; 268 | display: block; 269 | background-color: none; 270 | } 271 | // Dividers (basically an hr) within the dropdown 272 | .divider { 273 | height: 1px; 274 | margin: 5px 0; 275 | overflow: hidden; 276 | background-color: #eee; 277 | border-bottom: 1px solid @white; 278 | } 279 | } 280 | 281 | .topbar .dropdown-menu, 282 | .dropdown-menu { 283 | // Links within the dropdown menu 284 | a { 285 | display: block; 286 | padding: 4px 15px; 287 | clear: both; 288 | font-weight: normal; 289 | line-height: 18px; 290 | color: @gray; 291 | text-shadow: 0 1px 0 @white; 292 | // Hover state 293 | &:hover, 294 | &.hover { 295 | #gradient > .vertical(#eeeeee, #dddddd); 296 | color: @grayDark; 297 | text-decoration: none; 298 | @shadow: inset 0 1px 0 rgba(0,0,0,.025), inset 0 -1px rgba(0,0,0,.025); 299 | .box-shadow(@shadow); 300 | } 301 | } 302 | } 303 | 304 | // Open state for the dropdown 305 | // .open for backwards compatibility 306 | .open, 307 | .dropdown.open { 308 | // .menu for backwards compatibility 309 | .menu, 310 | .dropdown-toggle { 311 | color: @white; 312 | background: #ccc; 313 | background: rgba(0,0,0,.3); 314 | } 315 | // .menu-dropdown for backwards compatibility 316 | .menu-dropdown, 317 | .dropdown-menu { 318 | display: block; 319 | } 320 | } 321 | 322 | 323 | // TABS AND PILLS 324 | // -------------- 325 | 326 | // Common styles 327 | .tabs, 328 | .pills { 329 | margin: 0 0 @baseline; 330 | padding: 0; 331 | list-style: none; 332 | .clearfix(); 333 | > li { 334 | float: left; 335 | > a { 336 | display: block; 337 | } 338 | } 339 | } 340 | 341 | // Tabs 342 | .tabs { 343 | border-color: #ddd; 344 | border-style: solid; 345 | border-width: 0 0 1px; 346 | > li { 347 | position: relative; // For the dropdowns mostly 348 | margin-bottom: -1px; 349 | > a { 350 | padding: 0 15px; 351 | margin-right: 2px; 352 | line-height: (@baseline * 2) - 2; 353 | border: 1px solid transparent; 354 | .border-radius(4px 4px 0 0); 355 | &:hover { 356 | text-decoration: none; 357 | background-color: #eee; 358 | border-color: #eee #eee #ddd; 359 | } 360 | } 361 | } 362 | // Active state, and it's :hover to override normal :hover 363 | .active > a, 364 | .active > a:hover { 365 | color: @gray; 366 | background-color: @white; 367 | border: 1px solid #ddd; 368 | border-bottom-color: transparent; 369 | cursor: default; 370 | } 371 | } 372 | 373 | // Dropdowns in tabs 374 | .tabs { 375 | // first one for backwards compatibility 376 | .menu-dropdown, 377 | .dropdown-menu { 378 | top: 35px; 379 | border-width: 1px; 380 | .border-radius(0 6px 6px 6px); 381 | } 382 | // first one for backwards compatibility 383 | a.menu:after, 384 | .dropdown-toggle:after { 385 | border-top-color: #999; 386 | margin-top: 15px; 387 | margin-left: 5px; 388 | } 389 | // first one for backwards compatibility 390 | li.open.menu .menu, 391 | .open.dropdown .dropdown-toggle { 392 | border-color: #999; 393 | } 394 | // first one for backwards compatibility 395 | li.open a.menu:after, 396 | .dropdown.open .dropdown-toggle:after { 397 | border-top-color: #555; 398 | } 399 | } 400 | 401 | // Pills 402 | .pills { 403 | a { 404 | margin: 5px 3px 5px 0; 405 | padding: 0 15px; 406 | line-height: 30px; 407 | text-shadow: 0 1px 1px @white; 408 | .border-radius(15px); 409 | &:hover { 410 | color: @white; 411 | text-decoration: none; 412 | text-shadow: 0 1px 1px rgba(0,0,0,.25); 413 | background-color: @linkColorHover; 414 | } 415 | } 416 | .active a { 417 | color: @white; 418 | text-shadow: 0 1px 1px rgba(0,0,0,.25); 419 | background-color: @linkColor; 420 | } 421 | } 422 | 423 | // Stacked pills 424 | .pills-vertical > li { 425 | float: none; 426 | } 427 | 428 | // Tabbable areas 429 | .tab-content, 430 | .pill-content { 431 | } 432 | .tab-content > .tab-pane, 433 | .pill-content > .pill-pane, 434 | .tab-content > div, 435 | .pill-content > div { 436 | display: none; 437 | } 438 | .tab-content > .active, 439 | .pill-content > .active { 440 | display: block; 441 | } 442 | 443 | 444 | // BREADCRUMBS 445 | // ----------- 446 | 447 | .breadcrumb { 448 | padding: 7px 14px; 449 | margin: 0 0 @baseline; 450 | #gradient > .vertical(#ffffff, #f5f5f5); 451 | border: 1px solid #ddd; 452 | .border-radius(3px); 453 | .box-shadow(inset 0 1px 0 @white); 454 | li { 455 | display: inline; 456 | text-shadow: 0 1px 0 @white; 457 | } 458 | .divider { 459 | padding: 0 5px; 460 | color: @grayLight; 461 | } 462 | .active a { 463 | color: @grayDark; 464 | } 465 | } 466 | 467 | 468 | // PAGE HEADERS 469 | // ------------ 470 | 471 | .hero-unit { 472 | background-color: #f5f5f5; 473 | margin-bottom: 30px; 474 | padding: 60px; 475 | .border-radius(6px); 476 | h1 { 477 | margin-bottom: 0; 478 | font-size: 60px; 479 | line-height: 1; 480 | letter-spacing: -1px; 481 | } 482 | p { 483 | font-size: 18px; 484 | font-weight: 200; 485 | line-height: @baseline * 1.5; 486 | } 487 | } 488 | footer { 489 | margin-top: @baseline - 1; 490 | padding-top: @baseline - 1; 491 | border-top: 1px solid #eee; 492 | } 493 | 494 | 495 | // PAGE HEADERS 496 | // ------------ 497 | 498 | .page-header { 499 | margin-bottom: @baseline - 1; 500 | border-bottom: 1px solid #ddd; 501 | .box-shadow(0 1px 0 rgba(255,255,255,.5)); 502 | h1 { 503 | margin-bottom: (@baseline / 2) - 1px; 504 | } 505 | } 506 | 507 | 508 | // BUTTON STYLES 509 | // ------------- 510 | 511 | // Shared colors for buttons and alerts 512 | .btn, 513 | .alert-message { 514 | // Set text color 515 | &.danger, 516 | &.danger:hover, 517 | &.error, 518 | &.error:hover, 519 | &.success, 520 | &.success:hover, 521 | &.info, 522 | &.info:hover { 523 | color: @white 524 | } 525 | // Sets the close button to the middle of message 526 | .close{ 527 | font-family: Arial, sans-serif; 528 | line-height: 18px; 529 | } 530 | // Danger and error appear as red 531 | &.danger, 532 | &.error { 533 | .gradientBar(#ee5f5b, #c43c35); 534 | } 535 | // Success appears as green 536 | &.success { 537 | .gradientBar(#62c462, #57a957); 538 | } 539 | // Info appears as a neutral blue 540 | &.info { 541 | .gradientBar(#5bc0de, #339bb9); 542 | } 543 | } 544 | 545 | // Base .btn styles 546 | .btn { 547 | // Button Base 548 | cursor: pointer; 549 | display: inline-block; 550 | #gradient > .vertical-three-colors(#ffffff, #ffffff, 25%, darken(#ffffff, 10%)); // Don't use .gradientbar() here since it does a three-color gradient 551 | padding: 5px 14px 6px; 552 | text-shadow: 0 1px 1px rgba(255,255,255,.75); 553 | color: #333; 554 | font-size: @basefont; 555 | line-height: normal; 556 | border: 1px solid #ccc; 557 | border-bottom-color: #bbb; 558 | .border-radius(4px); 559 | @shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05); 560 | .box-shadow(@shadow); 561 | 562 | &:hover { 563 | background-position: 0 -15px; 564 | color: #333; 565 | text-decoration: none; 566 | } 567 | 568 | // Focus state for keyboard and accessibility 569 | &:focus { 570 | outline: 1px dotted #666; 571 | } 572 | 573 | // Primary Button Type 574 | &.primary { 575 | color: @white; 576 | .gradientBar(@blue, @blueDark) 577 | } 578 | 579 | // Transitions 580 | .transition(.1s linear all); 581 | 582 | // Active and Disabled states 583 | &.active, 584 | &:active { 585 | @shadow: inset 0 2px 4px rgba(0,0,0,.25), 0 1px 2px rgba(0,0,0,.05); 586 | .box-shadow(@shadow); 587 | } 588 | &.disabled { 589 | cursor: default; 590 | background-image: none; 591 | .reset-filter(); 592 | .opacity(65); 593 | .box-shadow(none); 594 | } 595 | &[disabled] { 596 | // disabled pseudo can't be included with .disabled 597 | // def because IE8 and below will drop it ;_; 598 | cursor: default; 599 | background-image: none; 600 | .reset-filter(); 601 | .opacity(65); 602 | .box-shadow(none); 603 | } 604 | 605 | // Button Sizes 606 | &.large { 607 | font-size: @basefont + 2px; 608 | line-height: normal; 609 | padding: 9px 14px 9px; 610 | .border-radius(6px); 611 | } 612 | &.small { 613 | padding: 7px 9px 7px; 614 | font-size: @basefont - 2px; 615 | } 616 | } 617 | // Super jank hack for removing border-radius from IE9 so we can keep filter gradients on alerts and buttons 618 | :root .alert-message, 619 | :root .btn { 620 | border-radius: 0 \0; 621 | } 622 | 623 | // Help Firefox not be a jerk about adding extra padding to buttons 624 | button.btn, 625 | input[type=submit].btn { 626 | &::-moz-focus-inner { 627 | padding: 0; 628 | border: 0; 629 | } 630 | } 631 | 632 | 633 | // CLOSE ICONS 634 | // ----------- 635 | .close { 636 | float: right; 637 | color: @black; 638 | font-size: 20px; 639 | font-weight: bold; 640 | line-height: @baseline * .75; 641 | text-shadow: 0 1px 0 rgba(255,255,255,1); 642 | .opacity(25); 643 | &:hover { 644 | color: @black; 645 | text-decoration: none; 646 | .opacity(40); 647 | } 648 | } 649 | 650 | 651 | // ERROR STYLES 652 | // ------------ 653 | 654 | // Base alert styles 655 | .alert-message { 656 | position: relative; 657 | padding: 7px 15px; 658 | margin-bottom: @baseline; 659 | color: @grayDark; 660 | .gradientBar(#fceec1, #eedc94); // warning by default 661 | text-shadow: 0 1px 0 rgba(255,255,255,.5); 662 | border-width: 1px; 663 | border-style: solid; 664 | .border-radius(4px); 665 | .box-shadow(inset 0 1px 0 rgba(255,255,255,.25)); 666 | 667 | // Adjust close icon 668 | .close { 669 | margin-top: 1px; 670 | *margin-top: 0; // For IE7 671 | } 672 | 673 | // Make links same color as text and stand out more 674 | a { 675 | font-weight: bold; 676 | color: @grayDark; 677 | } 678 | &.danger p a, 679 | &.error p a, 680 | &.success p a, 681 | &.info p a { 682 | color: @white; 683 | } 684 | 685 | // Remove extra margin from content 686 | h5 { 687 | line-height: @baseline; 688 | } 689 | p { 690 | margin-bottom: 0; 691 | } 692 | div { 693 | margin-top: 5px; 694 | margin-bottom: 2px; 695 | line-height: 28px; 696 | } 697 | .btn { 698 | // Provide actions with buttons 699 | .box-shadow(0 1px 0 rgba(255,255,255,.25)); 700 | } 701 | 702 | &.block-message { 703 | background-image: none; 704 | background-color: lighten(#fceec1, 5%); 705 | .reset-filter(); 706 | padding: 14px; 707 | border-color: #fceec1; 708 | .box-shadow(none); 709 | ul, p { 710 | margin-right: 30px; 711 | } 712 | ul { 713 | margin-bottom: 0; 714 | } 715 | li { 716 | color: @grayDark; 717 | } 718 | .alert-actions { 719 | margin-top: 5px; 720 | } 721 | &.error, 722 | &.success, 723 | &.info { 724 | color: @grayDark; 725 | text-shadow: 0 1px 0 rgba(255,255,255,.5); 726 | } 727 | &.error { 728 | background-color: lighten(#f56a66, 25%); 729 | border-color: lighten(#f56a66, 20%); 730 | } 731 | &.success { 732 | background-color: lighten(#62c462, 30%); 733 | border-color: lighten(#62c462, 25%); 734 | } 735 | &.info { 736 | background-color: lighten(#6bd0ee, 25%); 737 | border-color: lighten(#6bd0ee, 20%); 738 | } 739 | // Change link color back 740 | &.danger p a, 741 | &.error p a, 742 | &.success p a, 743 | &.info p a { 744 | color: @grayDark; 745 | } 746 | 747 | } 748 | } 749 | 750 | 751 | // PAGINATION 752 | // ---------- 753 | 754 | .pagination { 755 | height: @baseline * 2; 756 | margin: @baseline 0; 757 | ul { 758 | float: left; 759 | margin: 0; 760 | border: 1px solid #ddd; 761 | border: 1px solid rgba(0,0,0,.15); 762 | .border-radius(3px); 763 | .box-shadow(0 1px 2px rgba(0,0,0,.05)); 764 | } 765 | li { 766 | display: inline; 767 | } 768 | a { 769 | float: left; 770 | padding: 0 14px; 771 | line-height: (@baseline * 2) - 2; 772 | border-right: 1px solid; 773 | border-right-color: #ddd; 774 | border-right-color: rgba(0,0,0,.15); 775 | *border-right-color: #ddd; /* IE6-7 */ 776 | text-decoration: none; 777 | } 778 | a:hover, 779 | .active a { 780 | background-color: lighten(@blue, 45%); 781 | } 782 | .disabled a, 783 | .disabled a:hover { 784 | background-color: transparent; 785 | color: @grayLight; 786 | } 787 | .next a { 788 | border: 0; 789 | } 790 | } 791 | 792 | 793 | // WELLS 794 | // ----- 795 | 796 | .well { 797 | background-color: #f5f5f5; 798 | margin-bottom: 20px; 799 | padding: 19px; 800 | min-height: 20px; 801 | border: 1px solid #eee; 802 | border: 1px solid rgba(0,0,0,.05); 803 | .border-radius(4px); 804 | .box-shadow(inset 0 1px 1px rgba(0,0,0,.05)); 805 | blockquote { 806 | border-color: #ddd; 807 | border-color: rgba(0,0,0,.15); 808 | } 809 | } 810 | 811 | 812 | // MODALS 813 | // ------ 814 | 815 | .modal-backdrop { 816 | background-color: @black; 817 | position: fixed; 818 | top: 0; 819 | left: 0; 820 | right: 0; 821 | bottom: 0; 822 | z-index: 10000; 823 | // Fade for backdrop 824 | &.fade { opacity: 0; } 825 | } 826 | 827 | .modal-backdrop, 828 | .modal-backdrop.fade.in { 829 | .opacity(80); 830 | } 831 | 832 | .modal { 833 | position: fixed; 834 | top: 50%; 835 | left: 50%; 836 | z-index: 11000; 837 | width: 560px; 838 | margin: -250px 0 0 -280px; 839 | background-color: @white; 840 | border: 1px solid #999; 841 | border: 1px solid rgba(0,0,0,.3); 842 | *border: 1px solid #999; /* IE6-7 */ 843 | .border-radius(6px); 844 | .box-shadow(0 3px 7px rgba(0,0,0,0.3)); 845 | .background-clip(padding-box); 846 | .close { margin-top: 7px; } 847 | &.fade { 848 | .transition(e('opacity .3s linear, top .3s ease-out')); 849 | top: -25%; 850 | } 851 | &.fade.in { top: 50%; } 852 | } 853 | .modal-header { 854 | border-bottom: 1px solid #eee; 855 | padding: 5px 15px; 856 | } 857 | .modal-body { 858 | padding: 15px; 859 | } 860 | .modal-body form { 861 | margin-bottom: 0; 862 | } 863 | .modal-footer { 864 | background-color: #f5f5f5; 865 | padding: 14px 15px 15px; 866 | border-top: 1px solid #ddd; 867 | .border-radius(0 0 6px 6px); 868 | .box-shadow(inset 0 1px 0 @white); 869 | .clearfix(); 870 | margin-bottom: 0; 871 | .btn { 872 | float: right; 873 | margin-left: 5px; 874 | } 875 | } 876 | 877 | // Fix the stacking of these components when in modals 878 | .modal .popover, 879 | .modal .twipsy { 880 | z-index: 12000; 881 | } 882 | 883 | 884 | // POPOVER ARROWS 885 | // -------------- 886 | 887 | #popoverArrow { 888 | .above(@arrowWidth: 5px) { 889 | bottom: 0; 890 | left: 50%; 891 | margin-left: -@arrowWidth; 892 | border-left: @arrowWidth solid transparent; 893 | border-right: @arrowWidth solid transparent; 894 | border-top: @arrowWidth solid @black; 895 | } 896 | .left(@arrowWidth: 5px) { 897 | top: 50%; 898 | right: 0; 899 | margin-top: -@arrowWidth; 900 | border-top: @arrowWidth solid transparent; 901 | border-bottom: @arrowWidth solid transparent; 902 | border-left: @arrowWidth solid @black; 903 | } 904 | .below(@arrowWidth: 5px) { 905 | top: 0; 906 | left: 50%; 907 | margin-left: -@arrowWidth; 908 | border-left: @arrowWidth solid transparent; 909 | border-right: @arrowWidth solid transparent; 910 | border-bottom: @arrowWidth solid @black; 911 | } 912 | .right(@arrowWidth: 5px) { 913 | top: 50%; 914 | left: 0; 915 | margin-top: -@arrowWidth; 916 | border-top: @arrowWidth solid transparent; 917 | border-bottom: @arrowWidth solid transparent; 918 | border-right: @arrowWidth solid @black; 919 | } 920 | } 921 | 922 | // TWIPSY 923 | // ------ 924 | 925 | .twipsy { 926 | display: block; 927 | position: absolute; 928 | visibility: visible; 929 | padding: 5px; 930 | font-size: 11px; 931 | z-index: 1000; 932 | .opacity(80); 933 | &.fade.in { 934 | .opacity(80); 935 | } 936 | &.above .twipsy-arrow { #popoverArrow > .above(); } 937 | &.left .twipsy-arrow { #popoverArrow > .left(); } 938 | &.below .twipsy-arrow { #popoverArrow > .below(); } 939 | &.right .twipsy-arrow { #popoverArrow > .right(); } 940 | } 941 | .twipsy-inner { 942 | padding: 3px 8px; 943 | background-color: @black; 944 | color: white; 945 | text-align: center; 946 | max-width: 200px; 947 | text-decoration: none; 948 | .border-radius(4px); 949 | } 950 | .twipsy-arrow { 951 | position: absolute; 952 | width: 0; 953 | height: 0; 954 | } 955 | 956 | 957 | // POPOVERS 958 | // -------- 959 | 960 | .popover { 961 | position: absolute; 962 | top: 0; 963 | left: 0; 964 | z-index: 1000; 965 | padding: 5px; 966 | display: none; 967 | &.above .arrow { #popoverArrow > .above(); } 968 | &.right .arrow { #popoverArrow > .right(); } 969 | &.below .arrow { #popoverArrow > .below(); } 970 | &.left .arrow { #popoverArrow > .left(); } 971 | .arrow { 972 | position: absolute; 973 | width: 0; 974 | height: 0; 975 | } 976 | .inner { 977 | background: @black; 978 | background: rgba(0,0,0,.8); 979 | padding: 3px; 980 | overflow: hidden; 981 | width: 280px; 982 | .border-radius(6px); 983 | .box-shadow(0 3px 7px rgba(0,0,0,0.3)); 984 | } 985 | .title { 986 | background-color: #f5f5f5; 987 | padding: 9px 15px; 988 | line-height: 1; 989 | .border-radius(3px 3px 0 0); 990 | border-bottom:1px solid #eee; 991 | } 992 | .content { 993 | background-color: @white; 994 | padding: 14px; 995 | .border-radius(0 0 3px 3px); 996 | .background-clip(padding-box); 997 | p, ul, ol { 998 | margin-bottom: 0; 999 | } 1000 | } 1001 | } 1002 | 1003 | 1004 | // PATTERN ANIMATIONS 1005 | // ------------------ 1006 | 1007 | .fade { 1008 | .transition(opacity .15s linear); 1009 | opacity: 0; 1010 | &.in { 1011 | opacity: 1; 1012 | } 1013 | } 1014 | 1015 | 1016 | // LABELS 1017 | // ------ 1018 | 1019 | .label { 1020 | padding: 1px 3px 2px; 1021 | font-size: @basefont * .75; 1022 | font-weight: bold; 1023 | color: @white; 1024 | text-transform: uppercase; 1025 | white-space: nowrap; 1026 | background-color: @grayLight; 1027 | .border-radius(3px); 1028 | &.important { background-color: #c43c35; } 1029 | &.warning { background-color: @orange; } 1030 | &.success { background-color: @green; } 1031 | &.notice { background-color: lighten(@blue, 25%); } 1032 | } 1033 | 1034 | 1035 | // MEDIA GRIDS 1036 | // ----------- 1037 | 1038 | .media-grid { 1039 | margin-left: -@gridGutterWidth; 1040 | margin-bottom: 0; 1041 | .clearfix(); 1042 | li { 1043 | display: inline; 1044 | } 1045 | a { 1046 | float: left; 1047 | padding: 4px; 1048 | margin: 0 0 @baseline @gridGutterWidth; 1049 | border: 1px solid #ddd; 1050 | .border-radius(4px); 1051 | .box-shadow(0 1px 1px rgba(0,0,0,.075)); 1052 | img { 1053 | display: block; 1054 | } 1055 | &:hover { 1056 | border-color: @linkColor; 1057 | .box-shadow(0 1px 4px rgba(0,105,214,.25)); 1058 | } 1059 | } 1060 | } 1061 | -------------------------------------------------------------------------------- /lib/reset.less: -------------------------------------------------------------------------------- 1 | /* Reset.less 2 | * 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). 3 | * ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- */ 4 | 5 | 6 | // ERIC MEYER RESET 7 | // -------------------------------------------------- 8 | 9 | html, body { margin: 0; padding: 0; } 10 | 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; } 11 | table { border-collapse: collapse; border-spacing: 0; } 12 | ol, ul { list-style: none; } 13 | q:before, q:after, blockquote:before, blockquote:after { content: ""; } 14 | 15 | 16 | // Normalize.css 17 | // Pulling in select resets form the normalize.css project 18 | // -------------------------------------------------- 19 | 20 | // Display in IE6-9 and FF3 21 | // ------------------------- 22 | // Source: http://github.com/necolas/normalize.css 23 | html { 24 | overflow-y: scroll; 25 | font-size: 100%; 26 | -webkit-text-size-adjust: 100%; 27 | -ms-text-size-adjust: 100%; 28 | } 29 | // Focus states 30 | a:focus { 31 | outline: thin dotted; 32 | } 33 | // Hover & Active 34 | a:hover, 35 | a:active { 36 | outline: 0; 37 | } 38 | 39 | // Display in IE6-9 and FF3 40 | // ------------------------- 41 | // Source: http://github.com/necolas/normalize.css 42 | article, 43 | aside, 44 | details, 45 | figcaption, 46 | figure, 47 | footer, 48 | header, 49 | hgroup, 50 | nav, 51 | section { 52 | display: block; 53 | } 54 | 55 | // Display block in IE6-9 and FF3 56 | // ------------------------- 57 | // Source: http://github.com/necolas/normalize.css 58 | audio, 59 | canvas, 60 | video { 61 | display: inline-block; 62 | *display: inline; 63 | *zoom: 1; 64 | } 65 | 66 | // Prevents modern browsers from displaying 'audio' without controls 67 | // ------------------------- 68 | // Source: http://github.com/necolas/normalize.css 69 | audio:not([controls]) { 70 | display: none; 71 | } 72 | 73 | // Prevents sub and sup affecting line-height in all browsers 74 | // ------------------------- 75 | // Source: http://github.com/necolas/normalize.css 76 | sub, 77 | sup { 78 | font-size: 75%; 79 | line-height: 0; 80 | position: relative; 81 | vertical-align: baseline; 82 | } 83 | sup { 84 | top: -0.5em; 85 | } 86 | sub { 87 | bottom: -0.25em; 88 | } 89 | 90 | // Img border in a's and image quality 91 | // ------------------------- 92 | // Source: http://github.com/necolas/normalize.css 93 | img { 94 | border: 0; 95 | -ms-interpolation-mode: bicubic; 96 | } 97 | 98 | // Forms 99 | // ------------------------- 100 | // Source: http://github.com/necolas/normalize.css 101 | 102 | // Font size in all browsers, margin changes, misc consistency 103 | button, 104 | input, 105 | select, 106 | textarea { 107 | font-size: 100%; 108 | margin: 0; 109 | vertical-align: baseline; 110 | *vertical-align: middle; 111 | } 112 | button, 113 | input { 114 | line-height: normal; // FF3/4 have !important on line-height in UA stylesheet 115 | *overflow: visible; // Inner spacing ie IE6/7 116 | } 117 | button::-moz-focus-inner, 118 | input::-moz-focus-inner { // Inner padding and border oddities in FF3/4 119 | border: 0; 120 | padding: 0; 121 | } 122 | button, 123 | input[type="button"], 124 | input[type="reset"], 125 | input[type="submit"] { 126 | cursor: pointer; // Cursors on all buttons applied consistently 127 | -webkit-appearance: button; // Style clicable inputs in iOS 128 | } 129 | input[type="search"] { // Appearance in Safari/Chrome 130 | -webkit-appearance: textfield; 131 | -webkit-box-sizing: content-box; 132 | -moz-box-sizing: content-box; 133 | box-sizing: content-box; 134 | } 135 | input[type="search"]::-webkit-search-decoration { 136 | -webkit-appearance: none; // Inner-padding issues in Chrome OSX, Safari 5 137 | } 138 | textarea { 139 | overflow: auto; // Remove vertical scrollbar in IE6-9 140 | vertical-align: top; // Readability and alignment cross-browser 141 | } -------------------------------------------------------------------------------- /lib/scaffolding.less: -------------------------------------------------------------------------------- 1 | /* 2 | * Scaffolding 3 | * Basic and global styles for generating a grid system, structural layout, and page templates 4 | * ------------------------------------------------------------------------------------------- */ 5 | 6 | 7 | // STRUCTURAL LAYOUT 8 | // ----------------- 9 | 10 | body { 11 | background-color: @white; 12 | margin: 0; 13 | #font > .sans-serif(normal,@basefont,@baseline); 14 | color: @grayDark; 15 | } 16 | 17 | // Container (centered, fixed-width layouts) 18 | .container { 19 | .fixed-container(); 20 | } 21 | 22 | // Fluid layouts (left aligned, with sidebar, min- & max-width content) 23 | .container-fluid { 24 | position: relative; 25 | min-width: 940px; 26 | padding-left: 20px; 27 | padding-right: 20px; 28 | .clearfix(); 29 | > .sidebar { 30 | position: absolute; 31 | top: 0; 32 | left: 20px; 33 | width: 220px; 34 | } 35 | // TODO in v2: rename this and .popover .content to be more specific 36 | > .content { 37 | margin-left: 240px; 38 | } 39 | } 40 | 41 | 42 | // BASE STYLES 43 | // ----------- 44 | 45 | // Links 46 | a { 47 | color: @linkColor; 48 | text-decoration: none; 49 | line-height: inherit; 50 | font-weight: inherit; 51 | &:hover { 52 | color: @linkColorHover; 53 | text-decoration: underline; 54 | } 55 | } 56 | 57 | // Quick floats 58 | .pull-right { 59 | float: right; 60 | } 61 | .pull-left { 62 | float: left; 63 | } 64 | 65 | // Toggling content 66 | .hide { 67 | display: none; 68 | } 69 | .show { 70 | display: block; 71 | } 72 | 73 | 74 | // GRID SYSTEM 75 | // ----------- 76 | // To customize the grid system, bring up the variables.less file and change the column count, size, and gutter there 77 | 78 | .row { 79 | .clearfix(); 80 | margin-left: -@gridGutterWidth; 81 | } 82 | 83 | // Find all .span# classes within .row and give them the necessary properties for grid columns (supported by all browsers back to IE7) 84 | // Credit to @dhg for the idea 85 | .row > [class*="span"] { 86 | .gridColumn(); 87 | } 88 | 89 | // Default columns 90 | .span1 { .columns(1); } 91 | .span2 { .columns(2); } 92 | .span3 { .columns(3); } 93 | .span4 { .columns(4); } 94 | .span5 { .columns(5); } 95 | .span6 { .columns(6); } 96 | .span7 { .columns(7); } 97 | .span8 { .columns(8); } 98 | .span9 { .columns(9); } 99 | .span10 { .columns(10); } 100 | .span11 { .columns(11); } 101 | .span12 { .columns(12); } 102 | .span13 { .columns(13); } 103 | .span14 { .columns(14); } 104 | .span15 { .columns(15); } 105 | .span16 { .columns(16); } 106 | 107 | // For optional 24-column grid 108 | .span17 { .columns(17); } 109 | .span18 { .columns(18); } 110 | .span19 { .columns(19); } 111 | .span20 { .columns(20); } 112 | .span21 { .columns(21); } 113 | .span22 { .columns(22); } 114 | .span23 { .columns(23); } 115 | .span24 { .columns(24); } 116 | 117 | // Offset column options 118 | .row { 119 | > .offset1 { .offset(1); } 120 | > .offset2 { .offset(2); } 121 | > .offset3 { .offset(3); } 122 | > .offset4 { .offset(4); } 123 | > .offset5 { .offset(5); } 124 | > .offset6 { .offset(6); } 125 | > .offset7 { .offset(7); } 126 | > .offset8 { .offset(8); } 127 | > .offset9 { .offset(9); } 128 | > .offset10 { .offset(10); } 129 | > .offset11 { .offset(11); } 130 | > .offset12 { .offset(12); } 131 | } 132 | 133 | // Unique column sizes for 16-column grid 134 | .span-one-third { width: 300px; } 135 | .span-two-thirds { width: 620px; } 136 | .row { 137 | > .offset-one-third { margin-left: 340px; } 138 | > .offset-two-thirds { margin-left: 660px; } 139 | } -------------------------------------------------------------------------------- /lib/tables.less: -------------------------------------------------------------------------------- 1 | /* 2 | * Tables.less 3 | * Tables for, you guessed it, tabular data 4 | * ---------------------------------------- */ 5 | 6 | 7 | // BASELINE STYLES 8 | // --------------- 9 | 10 | table { 11 | width: 100%; 12 | margin-bottom: @baseline; 13 | padding: 0; 14 | font-size: @basefont; 15 | border-collapse: collapse; 16 | th, 17 | td { 18 | padding: 10px 10px 9px; 19 | line-height: @baseline; 20 | text-align: left; 21 | } 22 | th { 23 | padding-top: 9px; 24 | font-weight: bold; 25 | vertical-align: middle; 26 | } 27 | td { 28 | vertical-align: top; 29 | border-top: 1px solid #ddd; 30 | } 31 | // When scoped to row, fix th in tbody 32 | tbody th { 33 | border-top: 1px solid #ddd; 34 | vertical-align: top; 35 | } 36 | } 37 | 38 | 39 | // CONDENSED VERSION 40 | // ----------------- 41 | .condensed-table { 42 | th, 43 | td { 44 | padding: 5px 5px 4px; 45 | } 46 | } 47 | 48 | 49 | // BORDERED VERSION 50 | // ---------------- 51 | 52 | .bordered-table { 53 | border: 1px solid #ddd; 54 | border-collapse: separate; // Done so we can round those corners! 55 | *border-collapse: collapse; /* IE7, collapse table to remove spacing */ 56 | .border-radius(4px); 57 | th + th, 58 | td + td, 59 | th + td { 60 | border-left: 1px solid #ddd; 61 | } 62 | thead tr:first-child th:first-child, 63 | tbody tr:first-child td:first-child { 64 | .border-radius(4px 0 0 0); 65 | } 66 | thead tr:first-child th:last-child, 67 | tbody tr:first-child td:last-child { 68 | .border-radius(0 4px 0 0); 69 | } 70 | tbody tr:last-child td:first-child { 71 | .border-radius(0 0 0 4px); 72 | } 73 | tbody tr:last-child td:last-child { 74 | .border-radius(0 0 4px 0); 75 | } 76 | } 77 | 78 | 79 | // TABLE CELL SIZES 80 | // ---------------- 81 | 82 | // This is a duplication of the main grid .columns() mixin, but subtracts 20px to account for input padding and border 83 | .tableColumns(@columnSpan: 1) { 84 | width: ((@gridColumnWidth - 20) * @columnSpan) + ((@gridColumnWidth - 20) * (@columnSpan - 1)); 85 | } 86 | table { 87 | // Default columns 88 | .span1 { .tableColumns(1); } 89 | .span2 { .tableColumns(2); } 90 | .span3 { .tableColumns(3); } 91 | .span4 { .tableColumns(4); } 92 | .span5 { .tableColumns(5); } 93 | .span6 { .tableColumns(6); } 94 | .span7 { .tableColumns(7); } 95 | .span8 { .tableColumns(8); } 96 | .span9 { .tableColumns(9); } 97 | .span10 { .tableColumns(10); } 98 | .span11 { .tableColumns(11); } 99 | .span12 { .tableColumns(12); } 100 | .span13 { .tableColumns(13); } 101 | .span14 { .tableColumns(14); } 102 | .span15 { .tableColumns(15); } 103 | .span16 { .tableColumns(16); } 104 | } 105 | 106 | 107 | // ZEBRA-STRIPING 108 | // -------------- 109 | 110 | // Default zebra-stripe styles (alternating gray and transparent backgrounds) 111 | .zebra-striped { 112 | tbody { 113 | tr:nth-child(odd) td, 114 | tr:nth-child(odd) th { 115 | background-color: #f9f9f9; 116 | } 117 | tr:hover td, 118 | tr:hover th { 119 | background-color: #f5f5f5; 120 | } 121 | } 122 | } 123 | 124 | table { 125 | // Tablesorting styles w/ jQuery plugin 126 | .header { 127 | cursor: pointer; 128 | &:after { 129 | content: ""; 130 | float: right; 131 | margin-top: 7px; 132 | border-width: 0 4px 4px; 133 | border-style: solid; 134 | border-color: #000 transparent; 135 | visibility: hidden; 136 | } 137 | } 138 | // Style the sorted column headers (THs) 139 | .headerSortUp, 140 | .headerSortDown { 141 | background-color: rgba(141,192,219,.25); 142 | text-shadow: 0 1px 1px rgba(255,255,255,.75); 143 | } 144 | // Style the ascending (reverse alphabetical) column header 145 | .header:hover { 146 | &:after { 147 | visibility:visible; 148 | } 149 | } 150 | // Style the descending (alphabetical) column header 151 | .headerSortDown, 152 | .headerSortDown:hover { 153 | &:after { 154 | visibility:visible; 155 | .opacity(60); 156 | } 157 | } 158 | // Style the ascending (reverse alphabetical) column header 159 | .headerSortUp { 160 | &:after { 161 | border-bottom: none; 162 | border-left: 4px solid transparent; 163 | border-right: 4px solid transparent; 164 | border-top: 4px solid #000; 165 | visibility:visible; 166 | .box-shadow(none); //can't add boxshadow to downward facing arrow :( 167 | .opacity(60); 168 | } 169 | } 170 | // Blue Table Headings 171 | .blue { 172 | color: @blue; 173 | border-bottom-color: @blue; 174 | } 175 | .headerSortUp.blue, 176 | .headerSortDown.blue { 177 | background-color: lighten(@blue, 40%); 178 | } 179 | // Green Table Headings 180 | .green { 181 | color: @green; 182 | border-bottom-color: @green; 183 | } 184 | .headerSortUp.green, 185 | .headerSortDown.green { 186 | background-color: lighten(@green, 40%); 187 | } 188 | // Red Table Headings 189 | .red { 190 | color: @red; 191 | border-bottom-color: @red; 192 | } 193 | .headerSortUp.red, 194 | .headerSortDown.red { 195 | background-color: lighten(@red, 50%); 196 | } 197 | // Yellow Table Headings 198 | .yellow { 199 | color: @yellow; 200 | border-bottom-color: @yellow; 201 | } 202 | .headerSortUp.yellow, 203 | .headerSortDown.yellow { 204 | background-color: lighten(@yellow, 40%); 205 | } 206 | // Orange Table Headings 207 | .orange { 208 | color: @orange; 209 | border-bottom-color: @orange; 210 | } 211 | .headerSortUp.orange, 212 | .headerSortDown.orange { 213 | background-color: lighten(@orange, 40%); 214 | } 215 | // Purple Table Headings 216 | .purple { 217 | color: @purple; 218 | border-bottom-color: @purple; 219 | } 220 | .headerSortUp.purple, 221 | .headerSortDown.purple { 222 | background-color: lighten(@purple, 40%); 223 | } 224 | } -------------------------------------------------------------------------------- /lib/type.less: -------------------------------------------------------------------------------- 1 | /* Typography.less 2 | * Headings, body text, lists, code, and more for a versatile and durable typography system 3 | * ---------------------------------------------------------------------------------------- */ 4 | 5 | 6 | // BODY TEXT 7 | // --------- 8 | 9 | p { 10 | #font > .shorthand(normal,@basefont,@baseline); 11 | margin-bottom: @baseline / 2; 12 | small { 13 | font-size: @basefont - 2; 14 | color: @grayLight; 15 | } 16 | } 17 | 18 | 19 | // HEADINGS 20 | // -------- 21 | 22 | h1, h2, h3, h4, h5, h6 { 23 | font-weight: bold; 24 | color: @grayDark; 25 | small { 26 | color: @grayLight; 27 | } 28 | } 29 | h1 { 30 | margin-bottom: @baseline; 31 | font-size: 30px; 32 | line-height: @baseline * 2; 33 | small { 34 | font-size: 18px; 35 | } 36 | } 37 | h2 { 38 | font-size: 24px; 39 | line-height: @baseline * 2; 40 | small { 41 | font-size: 14px; 42 | } 43 | } 44 | h3, h4, h5, h6 { 45 | line-height: @baseline * 2; 46 | } 47 | h3 { 48 | font-size: 18px; 49 | small { 50 | font-size: 14px; 51 | } 52 | } 53 | h4 { 54 | font-size: 16px; 55 | small { 56 | font-size: 12px; 57 | } 58 | } 59 | h5 { 60 | font-size: 14px; 61 | } 62 | h6 { 63 | font-size: 13px; 64 | color: @grayLight; 65 | text-transform: uppercase; 66 | } 67 | 68 | 69 | // COLORS 70 | // ------ 71 | 72 | // Unordered and Ordered lists 73 | ul, ol { 74 | margin: 0 0 @baseline 25px; 75 | } 76 | ul ul, 77 | ul ol, 78 | ol ol, 79 | ol ul { 80 | margin-bottom: 0; 81 | } 82 | ul { 83 | list-style: disc; 84 | } 85 | ol { 86 | list-style: decimal; 87 | } 88 | li { 89 | line-height: @baseline; 90 | color: @gray; 91 | } 92 | ul.unstyled { 93 | list-style: none; 94 | margin-left: 0; 95 | } 96 | 97 | // Description Lists 98 | dl { 99 | margin-bottom: @baseline; 100 | dt, dd { 101 | line-height: @baseline; 102 | } 103 | dt { 104 | font-weight: bold; 105 | } 106 | dd { 107 | margin-left: @baseline / 2; 108 | } 109 | } 110 | 111 | // MISC 112 | // ---- 113 | 114 | // Horizontal rules 115 | hr { 116 | margin: 20px 0 19px; 117 | border: 0; 118 | border-bottom: 1px solid #eee; 119 | } 120 | 121 | // Emphasis 122 | strong { 123 | font-style: inherit; 124 | font-weight: bold; 125 | } 126 | em { 127 | font-style: italic; 128 | font-weight: inherit; 129 | line-height: inherit; 130 | } 131 | .muted { 132 | color: @grayLight; 133 | } 134 | 135 | // Blockquotes 136 | blockquote { 137 | margin-bottom: @baseline; 138 | border-left: 5px solid #eee; 139 | padding-left: 15px; 140 | p { 141 | #font > .shorthand(300,14px,@baseline); 142 | margin-bottom: 0; 143 | } 144 | small { 145 | display: block; 146 | #font > .shorthand(300,12px,@baseline); 147 | color: @grayLight; 148 | &:before { 149 | content: '\2014 \00A0'; 150 | } 151 | } 152 | } 153 | 154 | // Addresses 155 | address { 156 | display: block; 157 | line-height: @baseline; 158 | margin-bottom: @baseline; 159 | } 160 | 161 | // Inline and block code styles 162 | code, pre { 163 | padding: 0 3px 2px; 164 | font-family: Monaco, Andale Mono, Courier New, monospace; 165 | font-size: 12px; 166 | .border-radius(3px); 167 | } 168 | code { 169 | background-color: lighten(@orange, 40%); 170 | color: rgba(0,0,0,.75); 171 | padding: 1px 3px; 172 | } 173 | pre { 174 | background-color: #f5f5f5; 175 | display: block; 176 | padding: (@baseline - 1) / 2; 177 | margin: 0 0 @baseline; 178 | line-height: @baseline; 179 | font-size: 12px; 180 | border: 1px solid #ccc; 181 | border: 1px solid rgba(0,0,0,.15); 182 | .border-radius(3px); 183 | white-space: pre; 184 | white-space: pre-wrap; 185 | word-wrap: break-word; 186 | 187 | } -------------------------------------------------------------------------------- /lib/variables.less: -------------------------------------------------------------------------------- 1 | /* Variables.less 2 | * Variables to customize the look and feel of Bootstrap 3 | * ----------------------------------------------------- */ 4 | 5 | 6 | // Links 7 | @linkColor: %linkColor%; 8 | @linkColorHover: darken(@linkColor, 15); 9 | 10 | // Grays 11 | @black: %black%; 12 | @grayDark: lighten(@black, 25%); 13 | @gray: lighten(@black, 50%); 14 | @grayLight: lighten(@black, 75%); 15 | @grayLighter: lighten(@black, 90%); 16 | @white: %white%; 17 | 18 | // Accent Colors 19 | @blue: %blue%; 20 | @blueDark: %blueDark%; 21 | @green: %green%; 22 | @red: %red%; 23 | @yellow: %yellow%; 24 | @orange: %orange%; 25 | @pink: %pink%; 26 | @purple: %purple%; 27 | 28 | // Baseline grid 29 | @basefont: %basefont%px; 30 | @baseline: %baseline%px; 31 | 32 | // Griditude 33 | // Modify the grid styles in mixins.less 34 | @gridColumns: %gridColumns%; 35 | @gridColumnWidth: %gridColumnWidth%px; 36 | @gridGutterWidth: %gridGutterWidth%px; 37 | @extraSpace: (@gridGutterWidth * 2); // For our grid calculations 38 | @siteWidth: (@gridColumns * @gridColumnWidth) + (@gridGutterWidth * (@gridColumns - 1)); 39 | 40 | // Color Scheme 41 | // Use this to roll your own color schemes if you like (unused by Bootstrap by default) 42 | @baseColor: @blue; // Set a base color 43 | @complement: spin(@baseColor, 180); // Determine a complementary color 44 | @split1: spin(@baseColor, 158); // Split complements 45 | @split2: spin(@baseColor, -158); 46 | @triad1: spin(@baseColor, 135); // Triads colors 47 | @triad2: spin(@baseColor, -135); 48 | @tetra1: spin(@baseColor, 90); // Tetra colors 49 | @tetra2: spin(@baseColor, -90); 50 | @analog1: spin(@baseColor, 22); // Analogs colors 51 | @analog2: spin(@baseColor, -22); 52 | 53 | 54 | 55 | // More variables coming soon: 56 | // - @basefont to @baseFontSize 57 | // - @baseline to @baseLineHeight 58 | // - @baseFontFamily 59 | // - @primaryButtonColor 60 | // - anything else? File an issue on GitHub --------------------------------------------------------------------------------