-// Copyright 2014 Jason Woofenden -- Public Domain (CC0)
+// 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.
//
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.2em)
+space_evenly(line_height = 1.2)
+ line_height = unit(line_height, em)
text-align: justify
& > *
display: inline-block
// </nav>
// styl:
// nav li
-// sprite_rollover "images/nav.png" 150 35 2
+// 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)
- background-image: url(image)
+ 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)
// see sprites_rollover
sprites(image, height, count, v_offset = 0, h_offset = 0)
height: unit(height, px)
- background-image: url(image)
+ 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)
y = - (@height * n) - unit(v_offset, px)
background-position: (0 - unit(h_offset, px)) y
sprite(image, height, v_offset = 0, h_offset = 0)
- sprites(image, height, 1, v_offset, h_offset)
+ 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
-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
+
+// internal helper
+_pct_pos(offset, width, sheet_width)
+ if width is sheet_width
+ return 0
+ ratio = offset / (sheet_width - width)
+ if ratio is 0
+ return 0
+ return unit(ratio * 100, '%')
+
+// wfpl_spritesheets documentation/example:
+//
+// wfpl_spritesheets({
+// main: {
+// image: sha1['images/sprites.min.svg']
+// y_origin: 'bottom' // invert y coordinates below (thx inkscape)
+// w: 200 // width of document (whatever units)
+// h: 400 // height of document (whatever units)
+// sprites: {
+// ".icon": {
+// y: 399 // top (regardless of y_origin)
+// w: 48 // width of sprite (in doc coords)
+// h: 48 // height of sprite (in doc coords)
+// gap: 2 // space _between_ rows/columns
+// hover: 'right' // can be 'right' or 'down', meaning: direction to
+// // move in spritesheet to find the ":hover" graphic
+// column: 'three' 'four' 'five' 'six' // key can be 'column' or 'row'
+// // values are postfix for class names
+// }
+// ".logo": {
+// y: 100
+// w: 200
+// h: 50
+// }
+// }
+// }
+// })
+//
+// HTML:
+// <div class="logo"></span>
+// <span class="icon icon_three"></span>
+// <span class="icon icon_four"></span>
+//
+// CSS:
+// .icon
+// display: inline-block
+// width: 10%
+wfpl_spritesheets(hash)
+ for unused_name, sheet in hash
+ for selector, args in sheet.sprites
+ if sheet.y_origin is 'bottom'
+ y = sheet.h - args.y
+ else
+ y = args.y
+ if args.x
+ x = args.x
+ else
+ x = 0
+ if args.gap
+ gap = args.gap
+ else
+ gap = 0
+ w = args.w
+ h = args.h
+ if args.hover is 'right'
+ hdx = args.w + gap
+ hdy = 0
+ else if args.hover is 'down'
+ hdx = 0
+ hdy = args.h + gap
+ if not (args.column or args.row)
+ selector_before = selector + ":before"
+ {selector_before}
+ content: ''
+ display: block
+ background-position: _pct_pos(x, w, sheet.w) _pct_pos(y, h, sheet.h)
+ background-image: url((sheet['image']))
+ background-size: unit(sheet['w'] / w * 100, '%') auto
+ background-repeat: no-repeat
+ padding-top: unit(h / w * 100, '%')
+ selector_hover_before = selector + ":hover:before"
+ if args.hover
+ {selector_hover_before}
+ background-position: _pct_pos(x + hdx, w, sheet.w) _pct_pos(y + hdy, h, sheet.h)
+ else
+ names = ()
+ if args.column
+ dx = 0
+ dy = args.h + gap
+ for n in args.column
+ push(names, n)
+ else if args.row
+ dx = args.w + gap
+ dy = 0
+ for n in args.row
+ push(names, n)
+ d = 0
+ for postfix in names
+ selector_n_before = selector + "_" + postfix + ":before"
+ {selector_n_before}
+ background-position: _pct_pos(x + d * dx, w, sheet.w) _pct_pos(y + d * dy, h, sheet.h)
+ if args.hover
+ selector_n_hover_before = selector + "_" + postfix + ":hover:before"
+ {selector_n_hover_before}
+ background-position: _pct_pos(x + hdx + d * dx, w, sheet.w) _pct_pos(y + hdy + d * dy, h, sheet.h)
+ d += 1
+ selector_before = selector + ":before"
+ {selector_before}
+ content: ''
+ display: block
+ background-image: url((sheet['image']))
+ background-size: unit(sheet['w'] / w * 100, '%') auto
+ padding-top: unit(h / w * 100, '%')
+ return ret