// This program is in the public domain within the United States. Additionally,
// we waive copyright and related rights in the work worldwide through the CC0
// 1.0 Universal public domain dedication, which can be found at
// http://creativecommons.org/publicdomain/zero/1.0/
// This file contains helpers for using stylus in your project.
//
// Put something like this in your styl.styl:
// @require 'inc/wfpl/stylus-helpers.styl'
// set units to px if it doesn't have a unit already
_px(i)
unit(i) == '' ? unit(i, px) : i
// transparently support vender-specific tags
border-radius()
-webkit-border-radius: arguments
border-radius: arguments
box-shadow()
-webkit-box-shadow: arguments
box-shadow: arguments
box-sizing()
-webkit-box-sizing: arguments
-moz-box-sizing: arguments
box-sizing: arguments
user-select()
-webkit-user-select: arguments
-moz-user-select: arguments
-ms-user-select: arguments
user-select: arguments
// map "whitespace:" to "white-space:"
whitespace()
white-space: arguments
// helper function
_pos(type, args)
position: unquote(type)
for i in (0...length(args))
if not args[i] is a 'unit'
{args[i]}: args[i + 1] is a 'unit' ? args[i + 1] : 0
// you can pass directions and units, or just directions. Examples:
// absolute: top 20px left 0
// fixed: top right
// relative: top -4px
absolute()
_pos('absolute', arguments)
fixed()
_pos('fixed', arguments)
relative()
_pos('relative', arguments)
// Specify width and height on the same line
dimensions(w, h)
width: _px(w)
height: _px(h)
// generate (return) a "calc(Fpx + R%)" to scale linearly with parent
// args:
// pb: pixel width of parent (when biggest)
// ps: pixel width of parent (when smallest)
// cb: pixel width of child (when biggest)
// cs: pixel width of child (when smallest)
linear_scale_calc(pb, ps, cb, cs)
// math explained: (figuring out F and R in calc(Fpx + R%))
// f + r * pb = cb // known_1: formula(pb) -> cb
// f = cb - r * pb // solve_for_f: subtract (r * pb) from both sides
// f + r * ps = cs // known_2: formula(pb) -> cb
// cb - r * pb + r * ps = cs // substitute solved_for_f into known_2
// -r * pb + r * ps = cs - cb // subtract cb from both sides
// r * (-pb + ps) = cs - cb // factor out r from left side
// r = (cs - cb) / (-pb + ps) // divide both sides by (-pb + ps)
// r = (cb - cs) / (pb - ps) // multiply left side by -1/-1
r = (cb - cs) / (pb - ps)
f = cb - r * pb
unquote("calc(" + unit(f, "px") + ' + ' + unit(100 * r, "%") + ")")
// Make children of this element inline-blocks that are spaced evenly accross.
//
// To create a minimum distance between: don't use word-spacing, it's broken in
// firefox. Instead, try padding on the children and negative margin on the
// parent.
space_evenly(line_height = 1.2)
line_height = unit(line_height, em)
text-align: justify
& > *
display: inline-block
relative: top line_height
&:before
content: ''
display: block
width: 100%
margin-bottom: -(line_height)
&:after
content: ''
display: inline-block
width: 100%
// image layout: left column has normal, right column hover versions
//
// arguments: width/height are pixel dimensions of a single sprite
//
// markup: put classes n1, n2, etc on the items.
//
// Example
//
// html:
//
// styl:
// nav li
// sprites_rollover "images/nav.png" 150 35 2
sprites_rollover(image, width, height, count, v_offset = 0, h_offset = 0)
width: unit(width, px)
height: unit(height, px)
if image[1]
background: url(image[0])
background: url(image[1]), linear-gradient(transparent, transparent);
else
background-image: url(image)
background-position: top left
background-repeat: no-repeat;
for n in (0...count)
&.n{n}
y = - (@height * n) - unit(v_offset, px)
background-position: (0 - unit(h_offset, px)) y
&:hover
background-position: (0 - unit(h_offset, px) - @width) y
sprite_rollover(image, width, height, v_offset = 0, h_offset = 0)
sprites_rollover(image, width, height, 1, v_offset, h_offset)
// see sprites_rollover
sprites(image, height, count, v_offset = 0, h_offset = 0)
height: unit(height, px)
if image[1]
background: url(image[0])
background: url(image[1]), linear-gradient(transparent, transparent);
else
background-image: url(image)
background-position: top left
background-repeat: no-repeat;
for n in (0...count)
&.n{n}
y = - (@height * n) - unit(v_offset, px)
background-position: (0 - unit(h_offset, px)) y
sprite(image, height, v_offset = 0, h_offset = 0)
height: unit(height, px)
if image[1]
background: url(image[0])
background: url(image[1]), linear-gradient(transparent, transparent);
else
background-image: url(image)
background-repeat: no-repeat;
background-position: (0 - unit(h_offset, px)) (0 - unit(v_offset, px))
// Styling for a variable height element with an image background where the
// middle repeats vertically. You must split your image into three images, and
// name them with "-top", "-mid" and "-bot" suffixes (right before the file
// extension.) then pass a filepath as the first argument to this function
// which does not have a suffix.
//
// Example:
//
// image files:
// i/foo-top.png (900x20)
// i/foo-mid.png (900xwhatever)
// i/foo-bot.png (900x30)
//
// dom layout:
...
//
// Stylus:
//
// #expando
// mangin: 10px auto;
// width: 900px;
// background_vertical_stretch("i/foo.png", 20, 30)
//
// Notes:
//
// You can (optionally) pass 1-4 additional arguments which
// (effectively) add padding to the inner most div. The default is to
// have the height and width of the inner div match exactly the outer
// one. Parameters you specify are relative to that (not the section
// filled with the "-mid" image).
background_vertical_stretch(image, top_height, bottom_height, top_pad = 0, right_pad = top_pad, bottom_pad = top_pad, left_pad = right_pad)
top_height = _px(top_height)
bottom_height = _px(bottom_height)
top_pad = _px(top_pad)
right_pad = _px(right_pad)
bottom_pad = _px(bottom_pad)
left_pad = _px(left_pad)
ext = extname(image)
path = pathjoin(dirname(image), basename(image, ext))
// bottom image (outermost block)
background: transparent url(path + '-bot' + ext)
padding-bottom: bottom_height + 1 // +1 to prevent margin collapsing
> *
background: transparent url(path + '-top' + ext) 0 0 no-repeat
padding-top: top_height + 1 // +1 to prevent margin collapsing
> * > *
background: transparent url(path + '-mid' + ext) 0 0 repeat-y;
margin-top: -1px; // correct for above +1 from top
margin-bottom: -1px; // correct for above +1 from bottom
padding: 1px; // to prevent margin collapsing
> * > * > *
// all "2px" below are to correct for 1px above padding and below
padding: 1px;
margin-top: 2px - top_height + top_pad
margin-left: left_pad - 2px
margin-right: right_pad - 2px
margin-bottom: 2px - bottom_height + bottom_pad
background: transparent;
li_reset()
margin: 0
padding: 0
list-style: none
// Example:
// input
// +placeholder()
// color: red
placeholder()
&::-webkit-input-placeholder
{block}
&:-moz-placeholder // firefox 4-18
{block}
&::-moz-placeholder // firefox 19+
{block}
&:-ms-input-placeholder // ie
{block}
// Example:
// div.button
// noselect()
noselect()
-webkit-touch-callout: none
-webkit-user-select: none
-khtml-user-select: none
-moz-user-select: none
-ms-user-select: none
user-select: none
// Do all the crazy math to get columns to fit properly
// and (optionally) scale for responsive designs.
//
// example:
//
// columns = wfpl_columns({
// type: 'node',
// name: 'centerer',
// margin: 15px,
// width: 940px,
// child: {
// type: 'node',
// name: 'middler',
// border-width: 1px,
// border-style: solid,
// border-color: black,
// padding-left: 30px,
// padding-right: 18px,
// child: {
// type: 'alternatives',
// full: {
// type: 'series',
// nav: {
// type: 'node'
// width: 219px
// float: left
// },
// main: {
// type: 'node'
// margin-left: 48px,
// width: 623px
// float: left
// }
// },
// with_sidebar: {
// type: 'series',
// nav: {
// type: 'node',
// width: 219px
// float: left
// },
// main: {
// type: 'node',
// margin-left: 48px,
// width: 343px
// float: left
// },
// sidebar: {
// type: 'alternatives',
// plain: {
// type: 'node'
// margin-left: 30px,
// width: 250px
// float: left
// },
// bordered: {
// type: 'node'
// margin-left: 30px,
// border-width: 1px,
// border-style: solid
// border-color: red
// padding: 15px,
// width: 218px
// float: left
// }
// }
// }
// }
// }
// })
// output css from column calculations
// for selector, css in columns.css
// body > {selector}
// {css}
// @media screen and (max-width: (columns.width))
// // output responsive css from column calculations
// for selector, css in columns.responsive_css
// body > {selector}
// {css}
//
// // as big as it can be
// body > .centerer
// width: auto
//
// // make sure that borders (which won't scale) and rounding errors don't
// // break the layout
// body > .centerer.full > .main,
// body > .centerer.with_sidebar > .sidebar.plain,
// body > .centerer.with_sidebar > .sidebar.bordered
// margin-right: -10px
//
// // switch to 1-column layout
// @media screen and (max-width: (single_column_max))
// for selector, css in columns.css
// body > {selector}
// if selector == '.centerer'
// margin-top: 0
// else if selector in hide_in_one_column_mode
// display: none
// else
// border: none
// float: none
// width: auto
// margin: 0
// padding: 0
// margin-top: columns['responsive_css']['.centerer']['margin']
wfpl_columns_helper(top, node, selector, parent_width, expected_width)
css_rules = {}
responsive_css_rules = {}
if node.type == 'node'
left_width = 0px
right_width = 0px
width = null // inner
outer_width = null
if node.name
if selector == ''
selector = '.' + node.name
else
selector += ' > .' + node.name
// callculate width and outer_width
for k, v in node
if match('^(margin|padding|border-width)$', k)
left_width += v
right_width += v
if match('^((margin|padding)-left)|(border-left-width)$', k)
left_width += v
if match('^((margin|padding)-right)|(border-right-width)$', k)
right_width += v
if k != 'type' && k != 'child' && k != 'name' && k != 'outer_width'
css_rules[k] = v
for k, v in node
if k == 'width'
width = v
if k == 'outer_width'
outer_width = v
css_rules['width'] = v - left_width - right_width
if (!width) && (!outer_width)
if parent_width
outer_width = parent_width
else
error("Couldn't figure out width of " + selector);
if outer_width && !width
width = outer_width - left_width - right_width
if width && !outer_width
outer_width = width + left_width + right_width
unless parent_width // should only happen at top level
parent_width = outer_width
top['css'][selector] = css_rules
for k, v in css_rules
if k != 'border-width' && k != 'border-left-width' && k != 'border-right-width'
if typeof(v) == 'unit' && unit(v) == 'px'
responsive_css_rules[k] = floor(unit((v / parent_width) * 100, '%'), 4)
top['responsive_css'][selector] = responsive_css_rules
if 'child' in node
child_width = wfpl_columns_helper(top, node.child, selector, width, width)
_columns_recurser(rargs)
if child_width != width
error(selector + " is the is wrong width. Expected: " + width + ", got: " + rargs.ret.w)
top['widths'][selector] = width
return outer_width
if node.type == 'alternatives'
for name, child in node
if name != 'type'
child_selector = selector + '.' + name
child_width = wfpl_columns_helper(top, child, child_selector, parent_width, expected_width)
if expected_width
if child_width != expected_width
error(child_selector + " is wrong width. Expected: " + expected_width + ", got: " + child_width)
else
expected_width = child_width
unless parent_width
parent_width = child_width
return expected_width
if node.type == 'series'
series_width = 0px
for name, child in node
if name != 'type'
if selector == ''
child_selector = '.' + name
else
child_selector = selector + ' > .' + name
child_width = wfpl_columns_helper(top, child, child_selector, parent_width, null)
series_width += child_width
if expected_width
if series_width != expected_width
error(selector + " series total wrong. expected: " + expected_width + ", got: " + series_width);
return series_width
return // never reached with valid data
wfpl_columns(hash)
top = { // contains settings, and is returned from porcelain
responsive: responsive
width: null
css: {}
responsive_css: {}
widths: {}
}
top.width = wfpl_columns_helper(top, hash, '', null, null)
return top