]> _ Git - ccgm.git/commitdiff
WIP #3413 @5
authorstephen@cubedesigners.com <stephen@cubedesigners.com@f5622870-0f3c-0410-866d-9cb505b7a8ef>
Thu, 5 Mar 2020 20:00:28 +0000 (20:00 +0000)
committerstephen@cubedesigners.com <stephen@cubedesigners.com@f5622870-0f3c-0410-866d-9cb505b7a8ef>
Thu, 5 Mar 2020 20:00:28 +0000 (20:00 +0000)
framework/application/controllers/NewsController.php
framework/application/models/News.php
framework/application/views/helpers/News.php
framework/application/views/scripts/common/body.phtml [new file with mode: 0644]
framework/application/views/scripts/news/index.phtml
js/news-article.js [new file with mode: 0644]
js/slick-lightbox.js [new file with mode: 0644]
less/common.less
less/news-article.less [new file with mode: 0644]
less/slick-lightbox.less [new file with mode: 0644]
less/utilities.less [new file with mode: 0644]

index 2db0df1216fc022d0019ef60a4910f9dcadf13c1..e3fba6158ad4b5f3cbb172b6abf15ffd007d2aef 100644 (file)
@@ -4,25 +4,26 @@ class NewsController extends CubeIT_Controller_PageController {
 
     public function indexAction() {
 
+        $this->view->headScript()->addSlickCarousel();
+        $this->view->headScript()->addScriptAndStyle('slick-lightbox');
         $this->view->headScript()->addScriptAndStyle('news-article');
 
         $parent = $this->view->currentPage->getParent();
         $parent_data = $this->getBootstrap()->getCMSDatasOfNavigationPage($parent);
         $this->view->datas = $parent_data;
 
-        $db = Zend_Db_Table::getDefaultAdapter();
-        $select = $db->select()->from(['n' => 'news'])
-                ->where('n.id = ?', $this->getRequest()->getParam('news_id'));
-        $query = $select->query();
+        $model = CCGM_Model_News::factory()->where('id = ?', $this->getRequest()->getParam('news_id'));
+        $article = $model->find();
 
-        //echo 'QUERY: '. (string) $select;
-
-        if (!$query->rowCount()) {
+        if (count($article) < 1) {
             $this->_404();
             return;
         }
 
-        $this->view->news = $query->fetch();
+        // Get the first and only array item
+        $article = reset($article);
+
+        $this->view->news = $article;
     }
 
 }
index 8f0ecff2bdcd2dd8125d119f68db29cd59984f33..71c016acc27875d4572dd7efe420f0e87e0f77be 100644 (file)
@@ -24,4 +24,72 @@ class CCGM_Model_News extends CubeIT_Model_Data_Table {
                $table->addColumn('pdf', 'text');
        }
 
+       // Get the excerpt, which is just the first line of the content
+    public function getExcerpt() {
+        return explode(PHP_EOL, $this->content)[0];
+       }
+
+       // Process YouTube URLs entered and get poster images
+       public function getVideoDetails() {
+           $video_list = trim($this->videos);
+           $videos = [];
+
+           if (empty($video_list)) return false;
+
+        $video_URLs = explode(PHP_EOL, $video_list);
+
+        foreach ($video_URLs as $video_URL) {
+            $video_ID = $this->getVideoID($video_URL);
+            if (!$video_ID) continue;
+
+            $video_image = $this->youtube_image($video_ID);
+            if (!$video_image) continue;
+
+            $videos[] = [
+                'url' => $video_URL,
+                'image' => $video_image,
+                'embed' => "https://www.youtube.com/embed/$video_ID",
+            ];
+        }
+               
+        return $videos;
+    }
+
+    public function getVideoID($URL) {
+
+           $pattern = '/^.*(?:youtu\.be\/|youtube(?:-nocookie)?\.com\/(?:(?:watch)?\?(?:.*&)?vi?=|(?:embed|v|vi|user)\/))([^\?&\"\'>]+)/';
+        preg_match($pattern, $URL, $matches);
+
+        if ($matches) {
+            return trim($matches[1]);
+        }
+
+        return false;
+    }
+
+    // We can't guarantee that the video's maxresdefault.jpg will exist (it's not created for non-HD uploads)
+    // So this function will find the highest res image available...
+    // Ref: https://stackoverflow.com/a/20655623
+    function youtube_image($id) {
+        $resolution = [
+            'maxresdefault',
+            'hqdefault', // Might have black bars
+            'mqdefault', // No black bars on this size normally
+            'sddefault',
+            'default'
+        ];
+
+        $url = false;
+
+        for ($x = 0; $x < sizeof($resolution); $x++) {
+            $url = 'https://img.youtube.com/vi/' . $id . '/' . $resolution[$x] . '.jpg';
+
+            // Make sure we get a 200 OK HTTP response
+            if (strpos(get_headers($url)[0], '200 OK') !== false) {
+                break;
+            }
+        }
+        return $url;
+    }
+
 }
index ff98d98bebddb2f70c301c7bab76a843d77742e7..7f341db92bfc3d1cc37df211e5e178a041890dab 100644 (file)
@@ -24,7 +24,7 @@ class CCGM_View_Helper_News extends CubeIT_View_Helper_Abstract {
                //$c = $this->dateTime($n->getDate(), CubeIT_Date::DAY . '/' . CubeIT_Date::MONTH . '/' . CubeIT_Date::YEAR);\r
                $c  = $this->htmlElement($n->getTitle(), 'h2');\r
                $c .= '<div class="acc">';\r
-               $c .= $this->markupDotclear($n->getContent());\r
+               $c .= $this->markupDotclear($n->getExcerpt());\r
                $c .= $this->linkCMS($n->getLink());\r
                $c .= '</div>';\r
                $c .= $this->link('Lire la suite', $URL, ['class' => 'read-more']);\r
diff --git a/framework/application/views/scripts/common/body.phtml b/framework/application/views/scripts/common/body.phtml
new file mode 100644 (file)
index 0000000..60fcfd9
--- /dev/null
@@ -0,0 +1,9 @@
+<?php
+
+echo '<body class="'. $this->bodyClass .'">' . "\n";
+echo $this->bannerCookies();
+echo $this->render('admin/mockup.phtml');
+echo $this->render('admin/nav.phtml');
+echo $this->render('common/content.phtml');
+echo $this->render('ajax/loader.phtml');
+echo "\n" . '</body>' . "\n";
index 704cac57ca4c6911e6dc6e418e00e8991b1dc51f..6613214c320c7034d2e4d5ed960a1434cbe7c1ee 100644 (file)
@@ -1,29 +1,37 @@
 <?php
 
+$this->bodyClass = 'page-news-article';
 $this->showtopimage = false;
 $this->showsidebar = false;
-$news = CubeIT_Util_CMS::unserialize($this->news);
+$news = $this->news;
 
 ?>
 
-<h1><?= $news->title ?></h1>
+<h2 class="font-light text-pink mb-4 text-4xl">Actualités</h2>
+
+<h1 class="font-light text-orange mb-10 text-5xl"><?= $news->getTitle() ?></h1>
 
 <div class="news-body">
 
-    <?php
-        foreach($news->image as $image) {
-            echo $this->imageProcess($image, '', 1000, null);
-        }
-    ?>
+    <div class="news-slider">
 
-    <?= $this->markupDotclear($news->content); ?>
+        <?php foreach(CubeIT_Util_Cms::unserialize($news->getImage()) as $image): ?>
+            <?php $image_URL = $this->imageProcess($image, '', 980, null, [], 'C', 'C', 'M', false, 'auto', null, 92, 'files', true); ?>
+            <a href="<?= $image_URL ?>" target="_blank">
+                <img class="news-slider-image" src="<?= $image_URL ?>">
+            </a>
+        <?php endforeach; ?>
 
-    <?= $this->linkCMS($news->link); ?>
+        <?php foreach ($news->getVideoDetails() as $video): ?>
+            <a href="<?= $video['embed'] ?>" target="_blank">
+                <img class="news-slider-image" src="<?= $video['image'] ?>">
+            </a>
+        <?php endforeach; ?>
 
-</div>
+    </div>
 
-<pre>
-<?php
-    print_r($news);
-?>
-</pre>
+    <?= $this->markupDotclear($news->getContent()); ?>
+
+    <?= $this->linkCMS($news->getLink()); ?>
+
+</div>
diff --git a/js/news-article.js b/js/news-article.js
new file mode 100644 (file)
index 0000000..8a626ce
--- /dev/null
@@ -0,0 +1,17 @@
+TO_LOAD_ONCE[TO_LOAD_ONCE.length] = 'load_news_article();';
+
+function load_news_article() {
+
+  var $slider = $('.news-slider');
+
+  // Base slider in page
+  $slider.slick({
+    slidesToShow: 1
+  });
+
+  // Slider for lightbox
+  $slider.each(function () {
+    $(this).slickLightbox();
+  });
+
+}
diff --git a/js/slick-lightbox.js b/js/slick-lightbox.js
new file mode 100644 (file)
index 0000000..499ee17
--- /dev/null
@@ -0,0 +1,335 @@
+// https://github.com/jongacnik/slick-lightbox/blob/master/dist/slick-lightbox.js
+
+'use strict';
+
+/*
+ * SlickLightbox documentation #
+
+Documentation generated by [CoffeeDoc](http://github.com/omarkhan/coffeedoc)
+ */
+(function($) {
+  var SlickLightbox, defaults;
+  SlickLightbox = (function() {
+
+    /*
+               The one and only class used.
+     */
+    function SlickLightbox(element, options) {
+      var that;
+      this.options = options;
+
+      /* Binds the plugin. */
+      this.element = $(element);
+      this.didInit = false;
+      that = this;
+      this.element.on('click.slickLightbox', this.options.itemSelector, function(e) {
+        e.preventDefault();
+        return that.init(that.element.find(that.options.itemSelector).index($(this)));
+      });
+    }
+
+    SlickLightbox.prototype.init = function(index) {
+
+      /* Creates the lightbox, opens it, binds events and calls `slick`. Accepts `index` of the element, that triggered it (so that we know, on which slide to start slick). */
+      this.didInit = true;
+      this.detectIE();
+      this.createModal(index);
+      this.bindEvents();
+      this.initSlick();
+      return this.open();
+    };
+
+    SlickLightbox.prototype.createModalItems = function(index) {
+
+      /* Creates individual slides to be used with slick. If `options.images` array is specified, it uses it's contents, otherwise loops through elements' `options.itemSelector`. */
+      var a, createItem, links;
+      if (this.options.images) {
+        links = $.map(this.options.images, function(img) {
+          return "<div class=\"slick-lightbox-slick-item\"><div class=\"slick-lightbox-slick-item-inner\"><img class=\"slick-lightbox-slick-img\" src=\"" + img + "\" /></div></div>";
+        });
+      } else {
+        createItem = (function(_this) {
+          return function(el) {
+            var caption, src;
+            caption = _this.getElementCaption(el);
+            src = _this.getElementSrc(el);
+            if (_this.detectImage(src)) {
+              return "<div class=\"slick-lightbox-slick-item\"><div class=\"slick-lightbox-slick-item-inner\"><img class=\"slick-lightbox-slick-img\" src=\"" + src + "\" />" + caption + "</div></div>";
+            } else {
+              return "<div class=\"slick-lightbox-slick-item\"><div class=\"slick-lightbox-slick-item-inner iframe\"><div class=\"slick-lightbox-slick-iframe-wrap\"><iframe class=\"slick-lightbox-slick-iframe\" src=\"" + src + "\" frameborder=\"0\" allowfullscreen></iframe>" + caption + "</div></div></div>";
+            }
+          };
+        })(this);
+        a = this.element.find(this.options.itemSelector);
+        if (index === 0 || index === -1) {
+          links = $.map(a, createItem);
+        } else {
+          links = $.map(a.slice(index), createItem);
+          $.each(a.slice(0, index), function(i, el) {
+            return links.push(createItem(el));
+          });
+        }
+      }
+      return links;
+    };
+
+    SlickLightbox.prototype.createModal = function(index) {
+
+      /* Creates a `slick`-friendly modal. Rearranges the items so that the `index`-th item is placed first. */
+      var html, links;
+      links = this.createModalItems(index);
+      html = "<div class=\"slick-lightbox slick-hide-init" + (this.isIE ? ' slick-lightbox-ie' : '') + "\" style=\"background: " + this.options.background + ";\">\n   <div class=\"slick-lightbox-inner\">\n          <div class=\"slick-lightbox-slick slick-caption-" + this.options.captionPosition + "\">" + (links.join('')) + "</div>\n         <button type=\"button\" class=\"slick-lightbox-close\"></button>\n      <div>\n<div>";
+      this.modalElement = $(html);
+      return $('body').append(this.modalElement);
+    };
+
+    SlickLightbox.prototype.initSlick = function(index) {
+
+      /* Runs slick by default, using `options.slick` if provided. If `options.slick` is a function, it gets fired instead of us initializing slick. */
+      if (this.options.slick != null) {
+        if (typeof this.options.slick === 'function') {
+
+          /* Function expected to return slick instance. */
+          this.slick = this.options.slick(this.modalElement);
+        } else {
+          this.slick = this.modalElement.find('.slick-lightbox-slick').slick(this.options.slick);
+        }
+      } else {
+        this.slick = this.modalElement.find('.slick-lightbox-slick').slick();
+      }
+      return this.modalElement.trigger('init.slickLightbox');
+    };
+
+    SlickLightbox.prototype.open = function() {
+
+      /* Opens the lightbox. */
+      this.element.trigger('show.slickLightbox');
+      setTimeout(((function(_this) {
+        return function() {
+          return _this.element.trigger('shown.slickLightbox');
+        };
+      })(this)), this.getTransitionDuration());
+      return this.modalElement.removeClass('slick-hide-init');
+    };
+
+    SlickLightbox.prototype.close = function() {
+
+      /* Closes the lightbox and destroys it, maintaining the original element bindings. */
+      this.element.trigger('hide.slickLightbox');
+      setTimeout(((function(_this) {
+        return function() {
+          return _this.element.trigger('hidden.slickLightbox');
+        };
+      })(this)), this.getTransitionDuration());
+      this.modalElement.addClass('slick-hide');
+      return this.destroy();
+    };
+
+    SlickLightbox.prototype.bindEvents = function() {
+
+      /* Binds global events. */
+      var resizeSlides;
+      resizeSlides = (function(_this) {
+        return function() {
+          var h;
+          h = _this.modalElement.find('.slick-lightbox-inner').height();
+          _this.modalElement.find('.slick-lightbox-slick-item').height(h);
+          return _this.modalElement.find('.slick-lightbox-slick-img').css('max-height', Math.round(0.9 * h));
+        };
+      })(this);
+      $(window).on('orientationchange.slickLightbox resize.slickLightbox', resizeSlides);
+      this.modalElement.on('init.slickLightbox', resizeSlides);
+      this.modalElement.on('destroy.slickLightbox', (function(_this) {
+        return function() {
+          return _this.destroy();
+        };
+      })(this));
+      this.element.on('destroy.slickLightbox', (function(_this) {
+        return function() {
+          return _this.destroy(true);
+        };
+      })(this));
+      this.modalElement.on('click.slickLightbox touchstart.slickLightbox', '.slick-lightbox-close', (function(_this) {
+        return function(e) {
+          e.preventDefault();
+          return _this.close();
+        };
+      })(this));
+      if (this.options.closeOnEscape || this.options.navigateByKeyboard) {
+        $(document).on('keydown.slickLightbox', (function(_this) {
+          return function(e) {
+            var code;
+            code = e.keyCode ? e.keyCode : e.which;
+            if (_this.options.navigateByKeyboard) {
+              if (code === 37) {
+                _this.slideSlick('left');
+              } else if (code === 39) {
+                _this.slideSlick('right');
+              }
+            }
+            if (_this.options.closeOnEscape) {
+              if (code === 27) {
+                return _this.close();
+              }
+            }
+          };
+        })(this));
+      }
+      if (this.options.closeOnBackdropClick) {
+        this.modalElement.on('click.slickLightbox touchstart.slickLightbox', '.slick-lightbox-slick-img', (function(_this) {
+          return function(e) {
+            return e.stopPropagation();
+          };
+        })(this));
+        this.modalElement.on('click.slickLightbox touchstart.slickLightbox', '.slick-lightbox-slick-iframe-wrap', (function(_this) {
+          return function(e) {
+            return e.stopPropagation();
+          };
+        })(this));
+        return this.modalElement.on('click.slickLightbox touchstart.slickLightbox', '.slick-lightbox-slick-item', (function(_this) {
+          return function(e) {
+            e.preventDefault();
+            return _this.close();
+          };
+        })(this));
+      }
+    };
+
+    SlickLightbox.prototype.slideSlick = function(direction) {
+
+      /* Moves the slick prev or next. */
+      if (direction === 'left') {
+        return this.slick.slick('slickPrev');
+      } else {
+        return this.slick.slick('slickNext');
+      }
+    };
+
+    SlickLightbox.prototype.detectIE = function() {
+
+      /* Detects usage of IE8 and lower. */
+      var ieversion;
+      this.isIE = false;
+      if (/MSIE (\d+\.\d+);/.test(navigator.userAgent)) {
+        ieversion = new Number(RegExp.$1);
+        if (ieversion < 9) {
+          return this.isIE = true;
+        }
+      }
+    };
+
+    SlickLightbox.prototype.getElementCaption = function(el) {
+
+      /* Returns caption for each slide based on the type of `options.caption`. */
+      var c;
+      if (!this.options.caption) {
+        return '';
+      }
+      c = (function() {
+        switch (typeof this.options.caption) {
+          case 'function':
+            return this.options.caption(el);
+          case 'string':
+            return $(el).data(this.options.caption);
+        }
+      }).call(this);
+      return "<span class=\"slick-lightbox-slick-caption\">" + c + "</span>";
+    };
+
+    SlickLightbox.prototype.getElementSrc = function(el) {
+
+      /* Returns src for each slide image based on the type of `options.src`. */
+      switch (typeof this.options.src) {
+        case 'function':
+          return this.options.src(el);
+        case 'string':
+          return $(el).attr(this.options.src);
+        default:
+          return el.href;
+      }
+    };
+
+    SlickLightbox.prototype.detectImage = function(url) {
+
+      /* Returns true if finds image file extension */
+      return url.match(/\.(jpeg|jpg|gif|png)$/) !== null;
+    };
+
+    SlickLightbox.prototype.unbindEvents = function() {
+
+      /* Unbinds global events. */
+      $(window).off('.slickLightbox');
+      $(document).off('.slickLightbox');
+      return this.modalElement.off('.slickLightbox');
+    };
+
+    SlickLightbox.prototype.destroy = function(unbindAnchors) {
+      if (unbindAnchors == null) {
+        unbindAnchors = false;
+      }
+
+      /* Destroys the lightbox and unbinds global events. If `true` is passed as an argument, unbinds the original element as well. */
+      if (this.didInit) {
+        this.unbindEvents();
+        setTimeout(((function(_this) {
+          return function() {
+            return _this.modalElement.remove();
+          };
+        })(this)), this.options.destroyTimeout);
+      }
+      if (unbindAnchors) {
+        this.element.off('.slickLightbox');
+        return this.element.off('.slickLightbox', this.options.itemSelector);
+      }
+    };
+
+    SlickLightbox.prototype.destroyPrevious = function() {
+
+      /* Destroys lightboxes currently in DOM. */
+      return $('body').children('.slick-lightbox').trigger('destroy.slickLightbox');
+    };
+
+    SlickLightbox.prototype.getTransitionDuration = function() {
+
+      /* Detects the transition duration to know when to remove stuff from DOM etc. */
+      var duration;
+      if (this.transitionDuration) {
+        return this.transitionDuration;
+      }
+      duration = this.modalElement.css('transition-duration');
+      if (typeof duration === 'undefined') {
+        return this.transitionDuration = 500;
+      } else {
+        return this.transitionDuration = duration.indexOf('ms') > -1 ? parseFloat(duration) : parseFloat(duration) * 1000;
+      }
+    };
+
+    return SlickLightbox;
+
+  })();
+  defaults = {
+    background: 'rgba(0,0,0,.8)',
+    closeOnEscape: true,
+    closeOnBackdropClick: true,
+    destroyTimeout: 500,
+    itemSelector: 'a',
+    navigateByKeyboard: true,
+    src: false,
+    caption: false,
+    captionPosition: 'dynamic',
+    images: false,
+    slick: {}
+  };
+  $.fn.slickLightbox = function(options) {
+
+    /* Fires the plugin. */
+    options = $.extend({}, defaults, options);
+    new SlickLightbox(this, options);
+    return this;
+  };
+  return $.fn.unslickLightbox = function() {
+
+    /* Removes everything. */
+    return $(this).trigger('destroy.slickLightbox');
+  };
+})(jQuery);
index dbe8150004fcc1bca8f47040475399f5a742bf26..1470861e7a907474f4bc642875e87d59b306ab8b 100644 (file)
@@ -1,4 +1,5 @@
 @import "constants.less";
+@import "utilities.less";
 
 * {
        max-height: 1000000px;
@@ -12,7 +13,7 @@ html {
 body {
        margin: 0;
        color: #5e5e5e;
-       font: 300 16px/20px @Ubuntu;
+       font: 300 16px/1.25 @Ubuntu;
        background-color: #fff;
        min-width: 990px;
        -webkit-text-size-adjust: 100%;
diff --git a/less/news-article.less b/less/news-article.less
new file mode 100644 (file)
index 0000000..ba8274a
--- /dev/null
@@ -0,0 +1,17 @@
+.page-news-article .breadcrumbs {
+  margin: 2rem 0;
+  padding-bottom: 0;
+}
+
+.news-slider {
+  &-image {
+    max-width: 490px;
+    display: block;
+    margin: 0 auto;
+  }
+
+  .slick-track {
+    display: flex;
+    align-items: center;
+  }
+}
diff --git a/less/slick-lightbox.less b/less/slick-lightbox.less
new file mode 100644 (file)
index 0000000..2893c15
--- /dev/null
@@ -0,0 +1 @@
+.slick-lightbox{position:fixed;top:0;left:0;z-index:9999;width:100%;height:100%;background:#000;-webkit-transition:opacity .5s ease;transition:opacity .5s ease}.slick-lightbox.slick-hide{opacity:0}.slick-lightbox.slick-hide-init{position:absolute;top:-9999px;opacity:0}.slick-lightbox.slick-lightbox-ie.slick-hide,.slick-lightbox.slick-lightbox-ie.slick-hide-init{-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";filter:alpha(opacity=0)}.slick-lightbox .slick-lightbox-inner{position:fixed;top:0;left:0;width:100%;height:100%}.slick-lightbox .slick-lightbox-inner .slick-lightbox-slick .slick-lightbox-slick-item{text-align:center;overflow:hidden}.slick-lightbox .slick-lightbox-inner .slick-lightbox-slick .slick-lightbox-slick-item:before{content:'';display:inline-block;height:100%;vertical-align:middle;margin-right:-0.25em}.slick-lightbox .slick-lightbox-inner .slick-lightbox-slick .slick-lightbox-slick-item .slick-lightbox-slick-item-inner{display:inline-block;vertical-align:middle;max-width:90%;max-height:90%}.slick-lightbox .slick-lightbox-inner .slick-lightbox-slick .slick-lightbox-slick-item .slick-lightbox-slick-item-inner.iframe{width:70%;height:90%}.slick-lightbox .slick-lightbox-inner .slick-lightbox-slick .slick-lightbox-slick-item .slick-lightbox-slick-item-inner .slick-lightbox-slick-img{margin:0 auto;display:block;max-width:90%;max-height:90%}.slick-lightbox .slick-lightbox-inner .slick-lightbox-slick .slick-lightbox-slick-item .slick-lightbox-slick-item-inner .slick-lightbox-slick-iframe-wrap{position:relative;top:50%;transform:translateY(-50%)}.slick-lightbox .slick-lightbox-inner .slick-lightbox-slick .slick-lightbox-slick-item .slick-lightbox-slick-item-inner .slick-lightbox-slick-iframe-wrap:before{content:'';display:block;padding-top:56.25%}.slick-lightbox .slick-lightbox-inner .slick-lightbox-slick .slick-lightbox-slick-item .slick-lightbox-slick-item-inner .slick-lightbox-slick-iframe-wrap .slick-lightbox-slick-caption{position:relative;top:30px}.slick-lightbox .slick-lightbox-inner .slick-lightbox-slick .slick-lightbox-slick-item .slick-lightbox-slick-item-inner .slick-lightbox-slick-iframe-wrap .slick-lightbox-slick-iframe{position:absolute;top:0;left:0;width:100%;height:100%;z-index:0}.slick-lightbox .slick-lightbox-inner .slick-lightbox-slick .slick-lightbox-slick-item .slick-lightbox-slick-item-inner .slick-lightbox-slick-caption{margin-top:10px;color:#fff}.slick-lightbox .slick-lightbox-inner .slick-lightbox-slick.slick-caption-bottom .slick-lightbox-slick-item .slick-lightbox-slick-caption{position:absolute;bottom:0;left:0;text-align:center;width:100%;margin-bottom:20px}.slick-lightbox .slick-lightbox-inner .slick-lightbox-slick.slick-caption-dynamic .slick-lightbox-slick-item .slick-lightbox-slick-caption{display:block;text-align:center}.slick-lightbox .slick-lightbox-inner .slick-lightbox-close{position:absolute;top:15px;right:15px;display:block;height:20px;width:20px;line-height:0;font-size:0;cursor:pointer;background:transparent;color:transparent;padding:0;border:none}.slick-lightbox .slick-lightbox-inner .slick-lightbox-close:focus{outline:none}.slick-lightbox .slick-lightbox-inner .slick-lightbox-close:before{font-family:"slick";font-size:20px;line-height:1;color:#fff;opacity:.85;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;content:'×'}.slick-lightbox .slick-loading .slick-list{background-color:transparent}.slick-lightbox .slick-prev{left:15px}.slick-lightbox .slick-next{right:15px}
diff --git a/less/utilities.less b/less/utilities.less
new file mode 100644 (file)
index 0000000..dfe57d1
--- /dev/null
@@ -0,0 +1,73 @@
+// Colours
+.text-orange { color: #f5810f; }
+.text-pink { color: #e30186; }
+
+// Font sizes
+.text-xs { font-size: .75rem; }
+.text-sm { font-size: .875rem; }
+.text-base { font-size: 1rem; }
+.text-lg { font-size: 1.125rem; }
+.text-xl { font-size: 1.25rem; }
+.text-2xl { font-size: 1.5rem; }
+.text-3xl { font-size: 1.875rem; }
+.text-4xl { font-size: 2.25rem; }
+.text-5xl { font-size: 3.25rem; }
+.text-6xl { font-size: 4rem; }
+
+// Font weights
+.font-light { font-weight: 300; }
+.font-medium { font-weight: 500; }
+
+// Line heights
+.leading-none { line-height: 1; }
+.leading-tight { line-height: 1.25; }
+.leading-snug { line-height: 1.375; }
+.leading-normal        { line-height: 1.5; }
+.leading-relaxed { line-height: 1.625; }
+.leading-loose { line-height: 2; }
+
+// Margins
+.m-0 { margin: 0; }
+.m-1 { margin: 0.25rem; }
+.m-2 { margin: 0.5rem; }
+.m-3 { margin: 0.75rem; }
+.m-4 { margin: 1rem; }
+.m-5 { margin: 1.25rem; }
+.m-6 { margin: 1.5rem; }
+.m-8 { margin: 2rem; }
+.m-10 { margin: 2.5rem; }
+.m-12 { margin: 3rem; }
+
+.mt-0 { margin-top: 0; }
+.mt-1 { margin-top: 0.25rem; }
+.mt-2 { margin-top: 0.5rem; }
+.mt-3 { margin-top: 0.75rem; }
+.mt-4 { margin-top: 1rem; }
+.mt-5 { margin-top: 1.25rem; }
+.mt-6 { margin-top: 1.5rem; }
+.mt-8 { margin-top: 2rem; }
+.mt-10 { margin-top: 2.5rem; }
+.mt-12 { margin-top: 3rem; }
+
+.mb-0 { margin-bottom: 0; }
+.mb-1 { margin-bottom: 0.25rem; }
+.mb-2 { margin-bottom: 0.5rem; }
+.mb-3 { margin-bottom: 0.75rem; }
+.mb-4 { margin-bottom: 1rem; }
+.mb-5 { margin-bottom: 1.25rem; }
+.mb-6 { margin-bottom: 1.5rem; }
+.mb-8 { margin-bottom: 2rem; }
+.mb-10 { margin-bottom: 2.5rem; }
+.mb-12 { margin-bottom: 3rem; }
+
+// Padding
+.p-0 { padding: 0; }
+.p-1 { padding: 0.25rem; }
+.p-2 { padding: 0.5rem; }
+.p-3 { padding: 0.75rem; }
+.p-4 { padding: 1rem; }
+.p-5 { padding: 1.25rem; }
+.p-6 { padding: 1.5rem; }
+.p-8 { padding: 2rem; }
+.p-10 { padding: 2.5rem; }
+.p-12 { padding: 3rem; }