JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
Fix db_get_value after mysql->mysqli upgrade
[wfpl.git] / stylus_helpers.styl
1 // This program is in the public domain within the United States. Additionally,
2 // we waive copyright and related rights in the work worldwide through the CC0
3 // 1.0 Universal public domain dedication, which can be found at
4 // http://creativecommons.org/publicdomain/zero/1.0/
5
6 // This file contains helpers for using stylus in your project.
7 //
8 // Put something like this in your styl.styl:
9 //     @require 'inc/wfpl/stylus-helpers.styl'
10
11
12 // set units to px if it doesn't have a unit already
13 _px(i)
14         unit(i) == '' ? unit(i, px) : i
15
16 // transparently support vender-specific tags
17 border-radius()
18         -webkit-border-radius: arguments
19         border-radius: arguments
20 box-shadow()
21         -webkit-box-shadow: arguments
22         box-shadow: arguments
23 box-sizing()
24         -webkit-box-sizing: arguments
25         -moz-box-sizing: arguments
26         box-sizing: arguments
27 user-select()
28         -webkit-user-select: arguments
29         -moz-user-select: arguments
30         -ms-user-select: arguments
31         user-select: arguments
32
33 // map "whitespace:" to "white-space:"
34 whitespace()
35         white-space: arguments
36
37 // helper function
38 _pos(type, args)
39         position: unquote(type)
40         for i in (0...length(args))
41                 if not args[i] is a 'unit'
42                         {args[i]}: args[i + 1] is a 'unit' ? args[i + 1] : 0
43
44 // you can pass directions and units, or just directions. Examples:
45 //     absolute: top 20px left 0
46 //     fixed: top right
47 //     relative: top -4px
48 absolute()
49         _pos('absolute', arguments)
50 fixed()
51         _pos('fixed', arguments)
52 relative()
53         _pos('relative', arguments)
54
55 // Specify width and height on the same line
56 dimensions(w, h)
57         width: _px(w)
58         height: _px(h)
59
60 // generate (return) a "calc(Fpx + R%)" to scale linearly with parent
61 // args:
62 //   pb: pixel width of parent (when biggest)
63 //   ps: pixel width of parent (when smallest)
64 //   cb: pixel width of child (when biggest)
65 //   cs: pixel width of child (when smallest)
66 linear_scale_calc(pb, ps, cb, cs)
67         // math explained: (figuring out F and R in calc(Fpx + R%))
68         // f + r * pb = cb   // known_1: formula(pb) -> cb
69         //     f = cb - r * pb    // solve_for_f: subtract (r * pb) from both sides
70         // f + r * ps = cs   // known_2: formula(pb) -> cb
71         //     cb - r * pb + r * ps = cs    // substitute solved_for_f into known_2
72         //     -r * pb + r * ps = cs - cb   // subtract cb from both sides
73         //     r * (-pb + ps) = cs - cb     // factor out r from left side
74         //     r = (cs - cb) / (-pb + ps)   // divide both sides by (-pb + ps)
75         //     r = (cb - cs) / (pb - ps)    // multiply left side by -1/-1
76         r = (cb - cs) / (pb - ps)
77         f = cb - r * pb
78         unquote("calc(" + unit(f, "px") + ' + ' + unit(100 * r, "%") + ")")
79
80 // Make children of this element inline-blocks that are spaced evenly accross.
81 //
82 // To create a minimum distance between: don't use word-spacing, it's broken in
83 // firefox. Instead, try padding on the children and negative margin on the
84 // parent.
85 space_evenly(line_height = 1.2)
86         line_height = unit(line_height, em)
87         text-align: justify
88         & > *
89                 display: inline-block
90                 relative: top line_height
91         &:before
92                 content: ''
93                 display: block
94                 width: 100%
95                 margin-bottom: -(line_height)
96         &:after
97                 content: ''
98                 display: inline-block
99                 width: 100%
100
101 // image layout: left column has normal, right column hover versions
102 //
103 // arguments: width/height are pixel dimensions of a single sprite
104 //
105 // markup: put classes n1, n2, etc on the items.
106 //
107 // Example
108 //
109 //     html:
110 //         <nav>
111 //             <ul>
112 //                 <li class="n0">home</li>
113 //                 <li class="n1">contact</li>
114 //             </ul>
115 //         </nav>
116 //     styl:
117 //         nav li
118 //             sprites_rollover "images/nav.png" 150 35 2
119 sprites_rollover(image, width, height, count, v_offset = 0, h_offset = 0)
120         width: unit(width, px)
121         height: unit(height, px)
122         if image[1]
123                 background: url(image[0])
124                 background: url(image[1]), linear-gradient(transparent, transparent);
125         else
126                 background-image: url(image)
127         background-position: top left
128         background-repeat: no-repeat;
129         for n in (0...count)
130                 &.n{n}
131                         y = - (@height * n) - unit(v_offset, px)
132                         background-position: (0 - unit(h_offset, px)) y
133                         &:hover
134                                 background-position: (0 - unit(h_offset, px) - @width) y
135 sprite_rollover(image, width, height, v_offset = 0, h_offset = 0)
136         sprites_rollover(image, width, height, 1, v_offset, h_offset)
137
138 // see sprites_rollover
139 sprites(image, height, count, v_offset = 0, h_offset = 0)
140         height: unit(height, px)
141         if image[1]
142                 background: url(image[0])
143                 background: url(image[1]), linear-gradient(transparent, transparent);
144         else
145                 background-image: url(image)
146         background-position: top left
147         background-repeat: no-repeat;
148         for n in (0...count)
149                 &.n{n}
150                         y = - (@height * n) - unit(v_offset, px)
151                         background-position: (0 - unit(h_offset, px)) y
152 sprite(image, height, v_offset = 0, h_offset = 0)
153         height: unit(height, px)
154         if image[1]
155                 background: url(image[0])
156                 background: url(image[1]), linear-gradient(transparent, transparent);
157         else
158                 background-image: url(image)
159         background-repeat: no-repeat;
160         background-position: (0 - unit(h_offset, px)) (0 - unit(v_offset, px))
161
162 // Styling for a variable height element with an image background where the
163 // middle repeats vertically. You must split your image into three images, and
164 // name them with "-top", "-mid" and "-bot" suffixes (right before the file
165 // extension.) then pass a filepath as the first argument to this function
166 // which does not have a suffix.
167 //
168 // Example:
169 //
170 //     image files:
171 //         i/foo-top.png  (900x20)
172 //         i/foo-mid.png  (900xwhatever)
173 //         i/foo-bot.png  (900x30)
174 //
175 //     dom layout: <div id="expando"><div><div><div>...</div></div></div></div>
176 //
177 //     Stylus:
178 //
179 //         #expando
180 //            mangin: 10px auto;
181 //            width: 900px;
182 //            background_vertical_stretch("i/foo.png", 20, 30)
183 //
184 //     Notes:
185 //
186 //         You can (optionally) pass 1-4 additional arguments which
187 //         (effectively) add padding to the inner most div. The default is to
188 //         have the height and width of the inner div match exactly the outer
189 //         one. Parameters you specify are relative to that (not the section
190 //         filled with the "-mid" image).
191 background_vertical_stretch(image, top_height, bottom_height, top_pad = 0, right_pad = top_pad, bottom_pad = top_pad, left_pad = right_pad)
192         top_height = _px(top_height)
193         bottom_height = _px(bottom_height)
194         top_pad = _px(top_pad)
195         right_pad = _px(right_pad)
196         bottom_pad = _px(bottom_pad)
197         left_pad = _px(left_pad)
198         ext = extname(image)
199         path = pathjoin(dirname(image), basename(image, ext))
200
201         // bottom image (outermost block)
202         background: transparent url(path + '-bot' + ext)
203         padding-bottom: bottom_height + 1 // +1 to prevent margin collapsing
204         > *
205                 background: transparent url(path + '-top' + ext) 0 0 no-repeat
206                 padding-top: top_height + 1 // +1 to prevent margin collapsing
207         > * > *
208                 background: transparent url(path + '-mid' + ext) 0 0 repeat-y;
209                 margin-top: -1px; // correct for above +1 from top
210                 margin-bottom: -1px; // correct for above +1 from bottom
211                 padding: 1px; // to prevent margin collapsing
212         > * > * > *
213                 // all "2px" below are to correct for 1px above padding and below
214                 padding: 1px;
215                 margin-top: 2px - top_height + top_pad
216                 margin-left: left_pad - 2px
217                 margin-right: right_pad - 2px
218                 margin-bottom: 2px - bottom_height + bottom_pad
219                 background: transparent;
220
221 li_reset()
222         margin: 0
223         padding: 0
224         list-style: none
225
226 // Example:
227 //     input
228 //         +placeholder()
229 //             color: red
230 placeholder()
231         &::-webkit-input-placeholder
232                 {block}
233         &:-moz-placeholder // firefox 4-18
234                 {block}
235         &::-moz-placeholder // firefox 19+
236                 {block}
237         &:-ms-input-placeholder // ie
238                 {block}
239
240 // Example:
241 //     div.button
242 //         noselect()
243 noselect()
244         -webkit-touch-callout: none
245         -webkit-user-select: none
246         -khtml-user-select: none
247         -moz-user-select: none
248         -ms-user-select: none
249         user-select: none
250
251 // Do all the crazy math to get columns to fit properly
252 // and (optionally) scale for responsive designs.
253 //
254 // example:
255 //
256 //      columns = wfpl_columns({
257 //              type: 'node',
258 //              name: 'centerer',
259 //              margin: 15px,
260 //              width: 940px,
261 //              child: {
262 //                      type: 'node',
263 //                      name: 'middler',
264 //                      border-width: 1px,
265 //                      border-style: solid,
266 //                      border-color: black,
267 //                      padding-left: 30px,
268 //                      padding-right: 18px,
269 //                      child: {
270 //                              type: 'alternatives',
271 //                              full: {
272 //                                      type: 'series',
273 //                                      nav: {
274 //                                              type: 'node'
275 //                                              width: 219px
276 //                                              float: left
277 //                                      },
278 //                                      main: {
279 //                                              type: 'node'
280 //                                              margin-left: 48px,
281 //                                              width: 623px
282 //                                              float: left
283 //                                      }
284 //                              },
285 //                              with_sidebar: {
286 //                                      type: 'series',
287 //                                      nav: {
288 //                                              type: 'node',
289 //                                              width: 219px
290 //                                              float: left
291 //                                      },
292 //                                      main: {
293 //                                              type: 'node',
294 //                                              margin-left: 48px,
295 //                                              width: 343px
296 //                                              float: left
297 //                                      },
298 //                                      sidebar: {
299 //                                              type: 'alternatives',
300 //                                              plain: {
301 //                                                      type: 'node'
302 //                                                      margin-left: 30px,
303 //                                                      width: 250px
304 //                                                      float: left
305 //                                              },
306 //                                              bordered: {
307 //                                                      type: 'node'
308 //                                                      margin-left: 30px,
309 //                                                      border-width: 1px,
310 //                                                      border-style: solid
311 //                                                      border-color: red
312 //                                                      padding: 15px,
313 //                                                      width: 218px
314 //                                                      float: left
315 //                                              }
316 //                                      }
317 //                              }
318 //                      }
319 //              }
320 //      })
321 // output css from column calculations
322 //      for selector, css in columns.css
323 //              body > {selector}
324 //                      {css}
325 //      @media screen and (max-width: (columns.width))
326 //              // output responsive css from column calculations
327 //              for selector, css in columns.responsive_css
328 //                      body > {selector}
329 //                              {css}
330 //
331 //              // as big as it can be
332 //              body > .centerer
333 //                      width: auto
334 //
335 //              // make sure that borders (which won't scale) and rounding errors don't
336 //              // break the layout
337 //              body > .centerer.full > .main,
338 //              body > .centerer.with_sidebar > .sidebar.plain,
339 //              body > .centerer.with_sidebar > .sidebar.bordered
340 //                      margin-right: -10px
341 //
342 //      // switch to 1-column layout
343 //      @media screen and (max-width: (single_column_max))
344 //              for selector, css in columns.css
345 //                      body > {selector}
346 //                              if selector == '.centerer'
347 //                                      margin-top: 0
348 //                              else if selector in hide_in_one_column_mode
349 //                                      display: none
350 //                              else
351 //                                      border: none
352 //                                      float: none
353 //                                      width: auto
354 //                                      margin: 0
355 //                                      padding: 0
356 //                                      margin-top: columns['responsive_css']['.centerer']['margin']
357 wfpl_columns_helper(top, node, selector, parent_width, expected_width)
358         css_rules = {}
359         responsive_css_rules = {}
360         if node.type == 'node'
361                 left_width = 0px
362                 right_width = 0px
363                 width = null // inner
364                 outer_width = null
365                 if node.name
366                         if selector == ''
367                                 selector = '.' + node.name
368                         else
369                                 selector += ' > .' + node.name
370                 // callculate width and outer_width
371                 for k, v in node
372                         if match('^(margin|padding|border-width)$', k)
373                                 left_width += v
374                                 right_width += v
375                         if match('^((margin|padding)-left)|(border-left-width)$', k)
376                                 left_width += v
377                         if match('^((margin|padding)-right)|(border-right-width)$', k)
378                                 right_width += v
379                         if k != 'type' && k != 'child' && k != 'name' && k != 'outer_width'
380                                 css_rules[k] = v
381                 for k, v in node
382                         if k == 'width'
383                                 width = v
384                         if k == 'outer_width'
385                                 outer_width = v
386                                 css_rules['width'] = v - left_width - right_width
387                 if (!width) && (!outer_width)
388                         if parent_width
389                                 outer_width = parent_width
390                         else
391                                 error("Couldn't figure out width of " + selector);
392                 if outer_width && !width
393                         width = outer_width - left_width - right_width
394                 if width && !outer_width
395                         outer_width = width + left_width + right_width
396                 unless parent_width // should only happen at top level
397                         parent_width = outer_width
398                 top['css'][selector] = css_rules
399                 for k, v in css_rules
400                         if k != 'border-width' && k != 'border-left-width' && k != 'border-right-width'
401                                 if typeof(v) == 'unit' && unit(v) == 'px'
402                                         responsive_css_rules[k] = floor(unit((v / parent_width) * 100, '%'), 4)
403                 top['responsive_css'][selector] = responsive_css_rules
404                 if 'child' in node
405                         child_width = wfpl_columns_helper(top, node.child, selector, width, width)
406                         _columns_recurser(rargs)
407                         if child_width != width
408                                 error(selector + " is the is wrong width. Expected: " + width + ", got: " + rargs.ret.w)
409                 top['widths'][selector] = width
410                 return outer_width
411         if node.type == 'alternatives'
412                 for name, child in node
413                         if name != 'type'
414                                 child_selector = selector + '.' + name
415                                 child_width = wfpl_columns_helper(top, child, child_selector, parent_width, expected_width)
416                                 if expected_width
417                                         if child_width != expected_width
418                                                 error(child_selector + " is wrong width. Expected: " + expected_width + ", got: " + child_width)
419                                 else
420                                         expected_width = child_width
421                                 unless parent_width
422                                         parent_width = child_width
423                 return expected_width
424         if node.type == 'series'
425                 series_width = 0px
426                 for name, child in node
427                         if name != 'type'
428                                 if selector == ''
429                                         child_selector = '.' + name
430                                 else
431                                         child_selector = selector + ' > .' + name
432                                 child_width = wfpl_columns_helper(top, child, child_selector, parent_width, null)
433                                 series_width += child_width
434                 if expected_width
435                         if series_width != expected_width
436                                 error(selector + " series total wrong. expected: " + expected_width + ", got: " + series_width);
437                 return series_width
438         return // never reached with valid data
439
440 wfpl_columns(hash)
441         top = { // contains settings, and is returned from porcelain
442                 responsive: responsive
443                 width: null
444                 css: {}
445                 responsive_css: {}
446                 widths: {}
447         }
448         top.width = wfpl_columns_helper(top, hash, '', null, null)
449         return top
450
451 // internal helper
452 _pct_pos(offset, width, sheet_width)
453         if width is sheet_width
454                 return 0
455         ratio = offset / (sheet_width - width)
456         if ratio is 0
457                 return 0
458         return unit(ratio * 100, '%')
459
460 // wfpl_spritesheets documentation/example:
461 //
462 // wfpl_spritesheets({
463 //   main: {
464 //     image: sha1['images/sprites.min.svg']
465 //     y_origin: 'bottom' // invert y coordinates below (thx inkscape)
466 //     w: 200             // width of document (whatever units)
467 //     h: 400             // height of document (whatever units)
468 //     sprites: {
469 //       ".icon": {
470 //         y: 399         // top (regardless of y_origin)
471 //         w: 48          // width of sprite (in doc coords)
472 //         h: 48          // height of sprite (in doc coords)
473 //         gap: 2         // space _between_ rows/columns
474 //         hover: 'right' // can be 'right' or 'down', meaning: direction to
475 //                        // move in spritesheet to find the ":hover" graphic
476 //         column: 'three' 'four' 'five' 'six' // key can be 'column' or 'row'
477 //               // values are postfix for class names
478 //       }
479 //       ".logo": {
480 //         y: 100
481 //         w: 200
482 //         h: 50
483 //       }
484 //     }
485 //   }
486 // })
487 //
488 // HTML:
489 //   <div class="logo"></span>
490 //   <span class="icon icon_three"></span>
491 //   <span class="icon icon_four"></span>
492 //
493 // CSS:
494 //   .icon
495 //     display: inline-block
496 //     width: 10%
497 wfpl_spritesheets(hash)
498         for unused_name, sheet in hash
499                 for selector, args in sheet.sprites
500                         if sheet.y_origin is 'bottom'
501                                 y = sheet.h - args.y
502                         else
503                                 y = args.y
504                         if args.x
505                                 x = args.x
506                         else
507                                 x = 0
508                         if args.gap
509                                 gap = args.gap
510                         else
511                                 gap = 0
512                         w = args.w
513                         h = args.h
514                         if args.hover is 'right'
515                                 hdx = args.w + gap
516                                 hdy = 0
517                         else if args.hover is 'down'
518                                 hdx = 0
519                                 hdy = args.h + gap
520                         if not (args.column or args.row)
521                                 selector_before = selector + ":before"
522                                 {selector_before}
523                                         content: ''
524                                         display: block
525                                         background-position: _pct_pos(x, w, sheet.w) _pct_pos(y, h, sheet.h)
526                                         background-image: url((sheet['image']))
527                                         background-size: unit(sheet['w'] / w * 100, '%') auto
528                                         background-repeat: no-repeat
529                                         padding-top: unit(h / w * 100, '%')
530                                 selector_hover_before = selector + ":hover:before"
531                                 if args.hover
532                                         {selector_hover_before}
533                                                 background-position: _pct_pos(x + hdx, w, sheet.w) _pct_pos(y + hdy, h, sheet.h)
534                         else
535                                 names = ()
536                                 if args.column
537                                         dx = 0
538                                         dy = args.h + gap
539                                         for n in args.column
540                                                 push(names, n)
541                                 else if args.row
542                                         dx = args.w + gap
543                                         dy = 0
544                                         for n in args.row
545                                                 push(names, n)
546                                 d = 0
547                                 for postfix in names
548                                         selector_n_before = selector + "_" + postfix + ":before"
549                                         {selector_n_before}
550                                                 background-position: _pct_pos(x + d * dx, w, sheet.w) _pct_pos(y + d * dy, h, sheet.h)
551                                         if args.hover
552                                                 selector_n_hover_before = selector + "_" + postfix + ":hover:before"
553                                                 {selector_n_hover_before}
554                                                         background-position: _pct_pos(x + hdx + d * dx, w, sheet.w) _pct_pos(y + hdy + d * dy, h, sheet.h)
555                                         d += 1
556                                 selector_before = selector + ":before"
557                                 {selector_before}
558                                         content: ''
559                                         display: block
560                                         background-image: url((sheet['image']))
561                                         background-size: unit(sheet['w'] / w * 100, '%') auto
562                                         padding-top: unit(h / w * 100, '%')
563         return ret