diff --git a/.gitignore b/.gitignore index 4e6a03c..052e8c7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Composer composer.phar +composer.bat /vendor/ # IDE / Editor diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..ab2545e --- /dev/null +++ b/.htaccess @@ -0,0 +1,4 @@ +RewriteEngine on + +RewriteCond %{REQUEST_FILENAME} !-f +RewriteRule ^(.*)$ index.php?url=$1 [QSA,L] diff --git a/composer.json b/composer.json index fa5fc2c..45023ae 100644 --- a/composer.json +++ b/composer.json @@ -6,6 +6,12 @@ "email": "mat.micheli99@gmail.com" } ], + "autoload": { + "psr-4": { + "App\\" : "src", + "App\\Controller\\" : "src/Controller" + } + }, "require": { "twig/twig": "^3.0", "twbs/bootstrap": "^4.5" diff --git a/index.php b/index.php new file mode 100644 index 0000000..68baf0d --- /dev/null +++ b/index.php @@ -0,0 +1,9 @@ +get('/', 'Home#show'); +$router->run(); diff --git a/Controler/.gitignore b/src/Controller/.gitignore similarity index 100% rename from Controler/.gitignore rename to src/Controller/.gitignore diff --git a/src/Controller/HomeController.php b/src/Controller/HomeController.php new file mode 100644 index 0000000..a4bc428 --- /dev/null +++ b/src/Controller/HomeController.php @@ -0,0 +1,19 @@ + false//'src/tmp', + ]); + + echo $twig->render('home.html.twig'); + } +} diff --git a/Model/.gitignore b/src/Model/.gitignore similarity index 100% rename from Model/.gitignore rename to src/Model/.gitignore diff --git a/Public/.gitignore b/src/Public/.gitignore similarity index 100% rename from Public/.gitignore rename to src/Public/.gitignore diff --git a/src/Public/css/clean-blog.css b/src/Public/css/clean-blog.css new file mode 100644 index 0000000..ce594f7 --- /dev/null +++ b/src/Public/css/clean-blog.css @@ -0,0 +1,420 @@ +/*! + * Start Bootstrap - Clean Blog v5.0.9 (https://startbootstrap.com/themes/clean-blog) + * Copyright 2013-2020 Start Bootstrap + * Licensed under MIT (https://github.com/StartBootstrap/startbootstrap-clean-blog/blob/master/LICENSE) + */ + +body { + font-size: 20px; + color: #212529; + font-family: 'Montserrat', sans-serif; +} + +p { + line-height: 1.5; + margin: 30px 0; +} + +p a { + text-decoration: underline; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + font-weight: 800; + font-family: 'Roboto', sans-serif; +} + +a { + color: #212529; + transition: all 0.2s; +} + +a:focus, a:hover { + color: #0085A1; +} + +blockquote { + font-style: italic; + color: #868e96; +} + +.section-heading { + font-size: 36px; + font-weight: 700; + margin-top: 60px; +} + +.caption { + font-size: 14px; + font-style: italic; + display: block; + margin: 0; + padding: 10px; + text-align: center; + border-bottom-right-radius: 5px; + border-bottom-left-radius: 5px; +} + +::-moz-selection { + color: #fff; + background: #0085A1; + text-shadow: none; +} + +::selection { + color: #fff; + background: #0085A1; + text-shadow: none; +} + +img::-moz-selection { + color: #fff; + background: transparent; +} + +img::selection { + color: #fff; + background: transparent; +} + +img::-moz-selection { + color: #fff; + background: transparent; +} + +#mainNav { + position: absolute; + border-bottom: 1px solid #e9ecef; + background-color: white; + font-family: 'Roboto', sans-serif; +} + +#mainNav .navbar-brand { + font-weight: 800; + color: #343a40; +} + +#mainNav .navbar-toggler { + font-size: 12px; + font-weight: 800; + padding: 13px; + text-transform: uppercase; + color: #343a40; +} + +#mainNav .navbar-nav > li.nav-item > a { + font-size: 12px; + font-weight: 800; + letter-spacing: 1px; + text-transform: uppercase; +} + +@media only screen and (min-width: 992px) { + #mainNav { + border-bottom: 1px solid transparent; + background: transparent; + } + + #mainNav .navbar-brand { + padding: 10px 20px; + color: #fff; + } + + #mainNav .navbar-brand:focus, #mainNav .navbar-brand:hover { + color: rgba(255, 255, 255, 0.8); + } + + #mainNav .navbar-nav > li.nav-item > a { + padding: 10px 20px; + color: #fff; + } + + #mainNav .navbar-nav > li.nav-item > a:focus, #mainNav .navbar-nav > li.nav-item > a:hover { + color: rgba(255, 255, 255, 0.8); + } +} + +@media only screen and (min-width: 992px) { + #mainNav { + transition: background-color 0.2s; + /* Force Hardware Acceleration in WebKit */ + transform: translate3d(0, 0, 0); + -webkit-backface-visibility: hidden; + } + + #mainNav.is-fixed { + /* when the user scrolls down, we hide the header right above the viewport */ + position: fixed; + top: -67px; + transition: transform 0.2s; + border-bottom: 1px solid white; + background-color: rgba(255, 255, 255, 0.9); + } + + #mainNav.is-fixed .navbar-brand { + color: #212529; + } + + #mainNav.is-fixed .navbar-brand:focus, #mainNav.is-fixed .navbar-brand:hover { + color: #0085A1; + } + + #mainNav.is-fixed .navbar-nav > li.nav-item > a { + color: #212529; + } + + #mainNav.is-fixed .navbar-nav > li.nav-item > a:focus, #mainNav.is-fixed .navbar-nav > li.nav-item > a:hover { + color: #0085A1; + } + + #mainNav.is-visible { + /* if the user changes the scrolling direction, we show the header */ + transform: translate3d(0, 100%, 0); + } +} + +header.masthead { + margin-bottom: 50px; + background: no-repeat center center; + background-color: #868e96; + background-attachment: scroll; + position: relative; + background-size: cover; +} + +header.masthead .overlay { + position: absolute; + top: 0; + left: 0; + height: 100%; + width: 100%; + background-color: #212529; + opacity: 0.5; +} + +header.masthead .page-heading, +header.masthead .post-heading, +header.masthead .site-heading { + padding: 200px 0 150px; + color: white; +} + +@media only screen and (min-width: 768px) { + header.masthead .page-heading, + header.masthead .post-heading, + header.masthead .site-heading { + padding: 200px 0; + } +} + +header.masthead .page-heading, +header.masthead .site-heading { + text-align: center; +} + +header.masthead .page-heading h1, +header.masthead .site-heading h1 { + font-size: 50px; + margin-top: 0; +} + +header.masthead .page-heading .subheading, +header.masthead .site-heading .subheading { + font-size: 24px; + font-weight: 300; + line-height: 1.1; + display: block; + margin: 10px 0 0; + font-family: 'Roboto', sans-serif; +} + +@media only screen and (min-width: 768px) { + header.masthead .page-heading h1, + header.masthead .site-heading h1 { + font-size: 80px; + } +} + +header.masthead .post-heading h1 { + font-size: 35px; +} + +header.masthead .post-heading .meta, +header.masthead .post-heading .subheading { + line-height: 1.1; + display: block; +} + +header.masthead .post-heading .subheading { + font-size: 24px; + font-weight: 600; + margin: 10px 0 30px; + font-family: 'Roboto', sans-serif; +} + +header.masthead .post-heading .meta { + font-size: 20px; + font-weight: 300; + font-style: italic; + font-family: 'Montserrat', sans-serif; +} + +header.masthead .post-heading .meta a { + color: #fff; +} + +@media only screen and (min-width: 768px) { + header.masthead .post-heading h1 { + font-size: 55px; + } + + header.masthead .post-heading .subheading { + font-size: 30px; + } +} + +.post-preview > a { + color: #212529; +} + +.post-preview > a:focus, .post-preview > a:hover { + text-decoration: none; + color: #0085A1; +} + +.post-preview > a > .post-title { + font-size: 30px; + margin-top: 30px; + margin-bottom: 10px; +} + +.post-preview > a > .post-subtitle { + font-weight: 300; + margin: 0 0 10px; +} + +.post-preview > .post-meta { + font-size: 18px; + font-style: italic; + margin-top: 0; + color: #868e96; +} + +.post-preview > .post-meta > a { + text-decoration: none; + color: #212529; +} + +.post-preview > .post-meta > a:focus, .post-preview > .post-meta > a:hover { + text-decoration: underline; + color: #0085A1; +} + +@media only screen and (min-width: 768px) { + .post-preview > a > .post-title { + font-size: 36px; + } +} + +.floating-label-form-group { + font-size: 14px; + position: relative; + margin-bottom: 0; + padding-bottom: 0.5em; + border-bottom: 1px solid #dee2e6; +} + +.floating-label-form-group input, +.floating-label-form-group textarea { + font-size: 1.5em; + position: relative; + z-index: 1; + padding: 0; + resize: none; + border: none; + border-radius: 0; + background: none; + box-shadow: none !important; + font-family: 'Montserrat', sans-serif; +} + +.floating-label-form-group input::-webkit-input-placeholder, +.floating-label-form-group textarea::-webkit-input-placeholder { + color: #868e96; + font-family: 'Montserrat', sans-serif; +} + +.floating-label-form-group label { + font-size: 0.85em; + line-height: 1.764705882em; + position: relative; + z-index: 0; + top: 2em; + display: block; + margin: 0; + transition: top 0.3s ease, opacity 0.3s ease; + opacity: 0; +} + +.floating-label-form-group .help-block { + margin: 15px 0; +} + +.floating-label-form-group-with-value label { + top: 0; + opacity: 1; +} + +.floating-label-form-group-with-focus label { + color: #0085A1; +} + +form .form-group:first-child .floating-label-form-group { + border-top: 1px solid #dee2e6; +} + +footer { + padding: 50px 0 65px; +} + +footer .list-inline { + margin: 0; + padding: 0; +} + +footer .copyright { + font-size: 14px; + margin-bottom: 0; + text-align: center; +} + +.btn { + font-size: 14px; + font-weight: 800; + padding: 15px 25px; + letter-spacing: 1px; + text-transform: uppercase; + border-radius: 0; + font-family: 'Roboto', sans-serif; +} + +.btn-primary { + background-color: #0085A1; + border-color: #0085A1; +} + +.btn-primary:hover, .btn-primary:focus, .btn-primary:active { + color: #fff; + background-color: #00657b !important; + border-color: #00657b !important; +} + +.btn-lg { + font-size: 16px; + padding: 25px 35px; +} diff --git a/src/Public/css/clean-blog.min.css b/src/Public/css/clean-blog.min.css new file mode 100644 index 0000000..df4bddb --- /dev/null +++ b/src/Public/css/clean-blog.min.css @@ -0,0 +1,5 @@ +/*! + * Start Bootstrap - Clean Blog v5.0.9 (https://startbootstrap.com/themes/clean-blog) + * Copyright 2013-2020 Start Bootstrap + * Licensed under MIT (https://github.com/StartBootstrap/startbootstrap-clean-blog/blob/master/LICENSE) + */body{font-size:20px;color:#212529;font-family: 'Montserrat', sans-serif;}p{line-height:1.5;margin:30px 0}p a{text-decoration:underline}h1,h2,h3,h4,h5,h6{font-weight:800;font-family:'Roboto',sans-serif}a{color:#212529;transition:all .2s}a:focus,a:hover{color:#0085a1}blockquote{font-style:italic;color:#868e96}.section-heading{font-size:36px;font-weight:700;margin-top:60px}.caption{font-size:14px;font-style:italic;display:block;margin:0;padding:10px;text-align:center;border-bottom-right-radius:5px;border-bottom-left-radius:5px}::-moz-selection{color:#fff;background:#0085a1;text-shadow:none}::selection{color:#fff;background:#0085a1;text-shadow:none}img::-moz-selection{color:#fff;background:0 0}img::selection{color:#fff;background:0 0}img::-moz-selection{color:#fff;background:0 0}#mainNav{position:absolute;border-bottom:1px solid #e9ecef;background-color:#fff;font-family: 'Roboto', sans-serif;}#mainNav .navbar-brand{font-weight:800;color:#343a40}#mainNav .navbar-toggler{font-size:12px;font-weight:800;padding:13px;text-transform:uppercase;color:#343a40}#mainNav .navbar-nav>li.nav-item>a{font-size:12px;font-weight:800;letter-spacing:1px;text-transform:uppercase}@media only screen and (min-width:992px){#mainNav{border-bottom:1px solid transparent;background:0 0}#mainNav .navbar-brand{padding:10px 20px;color:#fff}#mainNav .navbar-brand:focus,#mainNav .navbar-brand:hover{color:rgba(255,255,255,.8)}#mainNav .navbar-nav>li.nav-item>a{padding:10px 20px;color:#fff}#mainNav .navbar-nav>li.nav-item>a:focus,#mainNav .navbar-nav>li.nav-item>a:hover{color:rgba(255,255,255,.8)}}@media only screen and (min-width:992px){#mainNav{transition:background-color .2s;transform:translate3d(0,0,0);-webkit-backface-visibility:hidden}#mainNav.is-fixed{position:fixed;top:-67px;transition:transform .2s;border-bottom:1px solid #fff;background-color:rgba(255,255,255,.9)}#mainNav.is-fixed .navbar-brand{color:#212529}#mainNav.is-fixed .navbar-brand:focus,#mainNav.is-fixed .navbar-brand:hover{color:#0085a1}#mainNav.is-fixed .navbar-nav>li.nav-item>a{color:#212529}#mainNav.is-fixed .navbar-nav>li.nav-item>a:focus,#mainNav.is-fixed .navbar-nav>li.nav-item>a:hover{color:#0085a1}#mainNav.is-visible{transform:translate3d(0,100%,0)}}header.masthead{margin-bottom:50px;background:no-repeat center center;background-color:#868e96;background-attachment:scroll;position:relative;background-size:cover}header.masthead .overlay{position:absolute;top:0;left:0;height:100%;width:100%;background-color:#212529;opacity:.5}header.masthead .page-heading,header.masthead .post-heading,header.masthead .site-heading{padding:200px 0 150px;color:#fff}@media only screen and (min-width:768px){header.masthead .page-heading,header.masthead .post-heading,header.masthead .site-heading{padding:200px 0}}header.masthead .page-heading,header.masthead .site-heading{text-align:center}header.masthead .page-heading h1,header.masthead .site-heading h1{font-size:50px;margin-top:0}header.masthead .page-heading .subheading,header.masthead .site-heading .subheading{font-size:24px;font-weight:300;line-height:1.1;display:block;margin:10px 0 0;font-family: 'Roboto', sans-serif;}@media only screen and (min-width:768px){header.masthead .page-heading h1,header.masthead .site-heading h1{font-size:80px}}header.masthead .post-heading h1{font-size:35px}header.masthead .post-heading .meta,header.masthead .post-heading .subheading{line-height:1.1;display:block}header.masthead .post-heading .subheading{font-size:24px;font-weight:600;margin:10px 0 30px;font-family: 'Roboto', sans-serif;}header.masthead .post-heading .meta{font-size:20px;font-weight:300;font-style:italic;font-family: 'Montserrat', sans-serif;}header.masthead .post-heading .meta a{color:#fff}@media only screen and (min-width:768px){header.masthead .post-heading h1{font-size:55px}header.masthead .post-heading .subheading{font-size:30px}}.post-preview>a{color:#212529}.post-preview>a:focus,.post-preview>a:hover{text-decoration:none;color:#0085a1}.post-preview>a>.post-title{font-size:30px;margin-top:30px;margin-bottom:10px}.post-preview>a>.post-subtitle{font-weight:300;margin:0 0 10px}.post-preview>.post-meta{font-size:18px;font-style:italic;margin-top:0;color:#868e96}.post-preview>.post-meta>a{text-decoration:none;color:#212529}.post-preview>.post-meta>a:focus,.post-preview>.post-meta>a:hover{text-decoration:underline;color:#0085a1}@media only screen and (min-width:768px){.post-preview>a>.post-title{font-size:36px}}.floating-label-form-group{font-size:14px;position:relative;margin-bottom:0;padding-bottom:.5em;border-bottom:1px solid #dee2e6}.floating-label-form-group input,.floating-label-form-group textarea{font-size:1.5em;position:relative;z-index:1;padding:0;resize:none;border:none;border-radius:0;background:0 0;box-shadow:none!important;font-family:'Montserrat',serif}.floating-label-form-group input::-webkit-input-placeholder,.floating-label-form-group textarea::-webkit-input-placeholder{color:#868e96;font-family:'Montserrat',serif}.floating-label-form-group label{font-size:.85em;line-height:1.764705882em;position:relative;z-index:0;top:2em;display:block;margin:0;transition:top .3s ease,opacity .3s ease;opacity:0}.floating-label-form-group .help-block{margin:15px 0}.floating-label-form-group-with-value label{top:0;opacity:1}.floating-label-form-group-with-focus label{color:#0085a1}form .form-group:first-child .floating-label-form-group{border-top:1px solid #dee2e6}footer{padding:50px 0 65px}footer .list-inline{margin:0;padding:0}footer .copyright{font-size:14px;margin-bottom:0;text-align:center}.btn{font-size:14px;font-weight:800;padding:15px 25px;letter-spacing:1px;text-transform:uppercase;border-radius:0;font-family: 'Roboto', sans-serif;}.btn-primary{background-color:#0085a1;border-color:#0085a1}.btn-primary:active,.btn-primary:focus,.btn-primary:hover{color:#fff;background-color:#00657b!important;border-color:#00657b!important}.btn-lg{font-size:16px;padding:25px 35px} \ No newline at end of file diff --git a/src/Public/css/style.css b/src/Public/css/style.css new file mode 100644 index 0000000..8e99a46 --- /dev/null +++ b/src/Public/css/style.css @@ -0,0 +1,19 @@ +h1, h2, h3, h4, h5, h6 +{ + font-family: 'Roboto', sans-serif;!important; +} +a, span, p +{ + font-family: 'Montserrat', sans-serif; +} + +.home-img +{ + height: 275px; + width: 345px; +} + +.test-div +{ + border: #e83e8c 3px solid; +} diff --git a/src/Public/files/CV.pdf b/src/Public/files/CV.pdf new file mode 100644 index 0000000..7193dcf Binary files /dev/null and b/src/Public/files/CV.pdf differ diff --git a/src/Public/img/about-bg.jpg b/src/Public/img/about-bg.jpg new file mode 100644 index 0000000..cd5302f Binary files /dev/null and b/src/Public/img/about-bg.jpg differ diff --git a/src/Public/img/contact-bg.jpg b/src/Public/img/contact-bg.jpg new file mode 100644 index 0000000..cf757fa Binary files /dev/null and b/src/Public/img/contact-bg.jpg differ diff --git a/src/Public/img/home-bg.jpg b/src/Public/img/home-bg.jpg new file mode 100644 index 0000000..26cd395 Binary files /dev/null and b/src/Public/img/home-bg.jpg differ diff --git a/src/Public/img/logo-home.webp b/src/Public/img/logo-home.webp new file mode 100644 index 0000000..1acbc2a Binary files /dev/null and b/src/Public/img/logo-home.webp differ diff --git a/src/Public/img/post-bg.jpg b/src/Public/img/post-bg.jpg new file mode 100644 index 0000000..03fe04a Binary files /dev/null and b/src/Public/img/post-bg.jpg differ diff --git a/src/Public/img/post-sample-image.jpg b/src/Public/img/post-sample-image.jpg new file mode 100644 index 0000000..3fc4282 Binary files /dev/null and b/src/Public/img/post-sample-image.jpg differ diff --git a/src/Public/js/clean-blog.js b/src/Public/js/clean-blog.js new file mode 100644 index 0000000..2930d5f --- /dev/null +++ b/src/Public/js/clean-blog.js @@ -0,0 +1,41 @@ +(function($) { + "use strict"; // Start of use strict + + // Floating label headings for the contact form + $("body").on("input propertychange", ".floating-label-form-group", function(e) { + $(this).toggleClass("floating-label-form-group-with-value", !!$(e.target).val()); + }).on("focus", ".floating-label-form-group", function() { + $(this).addClass("floating-label-form-group-with-focus"); + }).on("blur", ".floating-label-form-group", function() { + $(this).removeClass("floating-label-form-group-with-focus"); + }); + + // Show the navbar when the page is scrolled up + var MQL = 992; + + //primary navigation slide-in effect + if ($(window).width() > MQL) { + var headerHeight = $('#mainNav').height(); + $(window).on('scroll', { + previousTop: 0 + }, + function() { + var currentTop = $(window).scrollTop(); + //check if user is scrolling up + if (currentTop < this.previousTop) { + //if scrolling up... + if (currentTop > 0 && $('#mainNav').hasClass('is-fixed')) { + $('#mainNav').addClass('is-visible'); + } else { + $('#mainNav').removeClass('is-visible is-fixed'); + } + } else if (currentTop > this.previousTop) { + //if scrolling down... + $('#mainNav').removeClass('is-visible'); + if (currentTop > headerHeight && !$('#mainNav').hasClass('is-fixed')) $('#mainNav').addClass('is-fixed'); + } + this.previousTop = currentTop; + }); + } + +})(jQuery); // End of use strict diff --git a/src/Public/js/clean-blog.min.js b/src/Public/js/clean-blog.min.js new file mode 100644 index 0000000..489ca78 --- /dev/null +++ b/src/Public/js/clean-blog.min.js @@ -0,0 +1,7 @@ +/*! + * Start Bootstrap - Clean Blog v5.0.9 (https://startbootstrap.com/themes/clean-blog) + * Copyright 2013-2020 Start Bootstrap + * Licensed under MIT (https://github.com/StartBootstrap/startbootstrap-clean-blog/blob/master/LICENSE) + */ + +!function(o){"use strict";o("body").on("input propertychange",".floating-label-form-group",function(i){o(this).toggleClass("floating-label-form-group-with-value",!!o(i.target).val())}).on("focus",".floating-label-form-group",function(){o(this).addClass("floating-label-form-group-with-focus")}).on("blur",".floating-label-form-group",function(){o(this).removeClass("floating-label-form-group-with-focus")});if(992this.previousTop&&(o("#mainNav").removeClass("is-visible"),s - see LICENSE file + * + * http://ReactiveRaven.github.com/jqBootstrapValidation/ + */ + +(function($) { + + var createdElements = []; + + var defaults = { + options: { + prependExistingHelpBlock: false, + sniffHtml: true, // sniff for 'required', 'maxlength', etc + preventSubmit: true, // stop the form submit event from firing if validation fails + submitError: false, // function called if there is an error when trying to submit + submitSuccess: false, // function called just before a successful submit event is sent to the server + semanticallyStrict: false, // set to true to tidy up generated HTML output + autoAdd: { + helpBlocks: true + }, + filter: function() { + // return $(this).is(":visible"); // only validate elements you can see + return true; // validate everything + } + }, + methods: { + init: function(options) { + + var settings = $.extend(true, {}, defaults); + + settings.options = $.extend(true, settings.options, options); + + var $siblingElements = this; + + var uniqueForms = $.unique( + $siblingElements.map(function() { + return $(this).parents("form")[0]; + }).toArray() + ); + + $(uniqueForms).bind("submit", function(e) { + var $form = $(this); + var warningsFound = 0; + var $inputs = $form.find("input,textarea,select").not("[type=submit],[type=image]").filter(settings.options.filter); + $inputs.trigger("submit.validation").trigger("validationLostFocus.validation"); + + $inputs.each(function(i, el) { + var $this = $(el), + $controlGroup = $this.parents(".form-group").first(); + if ( + $controlGroup.hasClass("warning") + ) { + $controlGroup.removeClass("warning").addClass("error"); + warningsFound++; + } + }); + + $inputs.trigger("validationLostFocus.validation"); + + if (warningsFound) { + if (settings.options.preventSubmit) { + e.preventDefault(); + } + $form.addClass("error"); + if ($.isFunction(settings.options.submitError)) { + settings.options.submitError($form, e, $inputs.jqBootstrapValidation("collectErrors", true)); + } + } else { + $form.removeClass("error"); + if ($.isFunction(settings.options.submitSuccess)) { + settings.options.submitSuccess($form, e); + } + } + }); + + return this.each(function() { + + // Get references to everything we're interested in + var $this = $(this), + $controlGroup = $this.parents(".form-group").first(), + $helpBlock = $controlGroup.find(".help-block").first(), + $form = $this.parents("form").first(), + validatorNames = []; + + // create message container if not exists + if (!$helpBlock.length && settings.options.autoAdd && settings.options.autoAdd.helpBlocks) { + $helpBlock = $('
'); + $controlGroup.find('.controls').append($helpBlock); + createdElements.push($helpBlock[0]); + } + + // ============================================================= + // SNIFF HTML FOR VALIDATORS + // ============================================================= + + // *snort sniff snuffle* + + if (settings.options.sniffHtml) { + var message = ""; + // --------------------------------------------------------- + // PATTERN + // --------------------------------------------------------- + if ($this.attr("pattern") !== undefined) { + message = "Not in the expected format"; + if ($this.data("validationPatternMessage")) { + message = $this.data("validationPatternMessage"); + } + $this.data("validationPatternMessage", message); + $this.data("validationPatternRegex", $this.attr("pattern")); + } + // --------------------------------------------------------- + // MAX + // --------------------------------------------------------- + if ($this.attr("max") !== undefined || $this.attr("aria-valuemax") !== undefined) { + var max = ($this.attr("max") !== undefined ? $this.attr("max") : $this.attr("aria-valuemax")); + message = "Too high: Maximum of '" + max + "'"; + if ($this.data("validationMaxMessage")) { + message = $this.data("validationMaxMessage"); + } + $this.data("validationMaxMessage", message); + $this.data("validationMaxMax", max); + } + // --------------------------------------------------------- + // MIN + // --------------------------------------------------------- + if ($this.attr("min") !== undefined || $this.attr("aria-valuemin") !== undefined) { + var min = ($this.attr("min") !== undefined ? $this.attr("min") : $this.attr("aria-valuemin")); + message = "Too low: Minimum of '" + min + "'"; + if ($this.data("validationMinMessage")) { + message = $this.data("validationMinMessage"); + } + $this.data("validationMinMessage", message); + $this.data("validationMinMin", min); + } + // --------------------------------------------------------- + // MAXLENGTH + // --------------------------------------------------------- + if ($this.attr("maxlength") !== undefined) { + message = "Too long: Maximum of '" + $this.attr("maxlength") + "' characters"; + if ($this.data("validationMaxlengthMessage")) { + message = $this.data("validationMaxlengthMessage"); + } + $this.data("validationMaxlengthMessage", message); + $this.data("validationMaxlengthMaxlength", $this.attr("maxlength")); + } + // --------------------------------------------------------- + // MINLENGTH + // --------------------------------------------------------- + if ($this.attr("minlength") !== undefined) { + message = "Too short: Minimum of '" + $this.attr("minlength") + "' characters"; + if ($this.data("validationMinlengthMessage")) { + message = $this.data("validationMinlengthMessage"); + } + $this.data("validationMinlengthMessage", message); + $this.data("validationMinlengthMinlength", $this.attr("minlength")); + } + // --------------------------------------------------------- + // REQUIRED + // --------------------------------------------------------- + if ($this.attr("required") !== undefined || $this.attr("aria-required") !== undefined) { + message = settings.builtInValidators.required.message; + if ($this.data("validationRequiredMessage")) { + message = $this.data("validationRequiredMessage"); + } + $this.data("validationRequiredMessage", message); + } + // --------------------------------------------------------- + // NUMBER + // --------------------------------------------------------- + if ($this.attr("type") !== undefined && $this.attr("type").toLowerCase() === "number") { + message = settings.builtInValidators.number.message; + if ($this.data("validationNumberMessage")) { + message = $this.data("validationNumberMessage"); + } + $this.data("validationNumberMessage", message); + } + // --------------------------------------------------------- + // EMAIL + // --------------------------------------------------------- + if ($this.attr("type") !== undefined && $this.attr("type").toLowerCase() === "email") { + message = "Not a valid email address"; + if ($this.data("validationValidemailMessage")) { + message = $this.data("validationValidemailMessage"); + } else if ($this.data("validationEmailMessage")) { + message = $this.data("validationEmailMessage"); + } + $this.data("validationValidemailMessage", message); + } + // --------------------------------------------------------- + // MINCHECKED + // --------------------------------------------------------- + if ($this.attr("minchecked") !== undefined) { + message = "Not enough options checked; Minimum of '" + $this.attr("minchecked") + "' required"; + if ($this.data("validationMincheckedMessage")) { + message = $this.data("validationMincheckedMessage"); + } + $this.data("validationMincheckedMessage", message); + $this.data("validationMincheckedMinchecked", $this.attr("minchecked")); + } + // --------------------------------------------------------- + // MAXCHECKED + // --------------------------------------------------------- + if ($this.attr("maxchecked") !== undefined) { + message = "Too many options checked; Maximum of '" + $this.attr("maxchecked") + "' required"; + if ($this.data("validationMaxcheckedMessage")) { + message = $this.data("validationMaxcheckedMessage"); + } + $this.data("validationMaxcheckedMessage", message); + $this.data("validationMaxcheckedMaxchecked", $this.attr("maxchecked")); + } + } + + // ============================================================= + // COLLECT VALIDATOR NAMES + // ============================================================= + + // Get named validators + if ($this.data("validation") !== undefined) { + validatorNames = $this.data("validation").split(","); + } + + // Get extra ones defined on the element's data attributes + $.each($this.data(), function(i, el) { + var parts = i.replace(/([A-Z])/g, ",$1").split(","); + if (parts[0] === "validation" && parts[1]) { + validatorNames.push(parts[1]); + } + }); + + // ============================================================= + // NORMALISE VALIDATOR NAMES + // ============================================================= + + var validatorNamesToInspect = validatorNames; + var newValidatorNamesToInspect = []; + + do // repeatedly expand 'shortcut' validators into their real validators + { + // Uppercase only the first letter of each name + $.each(validatorNames, function(i, el) { + validatorNames[i] = formatValidatorName(el); + }); + + // Remove duplicate validator names + validatorNames = $.unique(validatorNames); + + // Pull out the new validator names from each shortcut + newValidatorNamesToInspect = []; + $.each(validatorNamesToInspect, function(i, el) { + if ($this.data("validation" + el + "Shortcut") !== undefined) { + // Are these custom validators? + // Pull them out! + $.each($this.data("validation" + el + "Shortcut").split(","), function(i2, el2) { + newValidatorNamesToInspect.push(el2); + }); + } else if (settings.builtInValidators[el.toLowerCase()]) { + // Is this a recognised built-in? + // Pull it out! + var validator = settings.builtInValidators[el.toLowerCase()]; + if (validator.type.toLowerCase() === "shortcut") { + $.each(validator.shortcut.split(","), function(i, el) { + el = formatValidatorName(el); + newValidatorNamesToInspect.push(el); + validatorNames.push(el); + }); + } + } + }); + + validatorNamesToInspect = newValidatorNamesToInspect; + + } while (validatorNamesToInspect.length > 0) + + // ============================================================= + // SET UP VALIDATOR ARRAYS + // ============================================================= + + var validators = {}; + + $.each(validatorNames, function(i, el) { + // Set up the 'override' message + var message = $this.data("validation" + el + "Message"); + var hasOverrideMessage = (message !== undefined); + var foundValidator = false; + message = + ( + message ? + message : + "'" + el + "' validation failed " + ); + + $.each( + settings.validatorTypes, + function(validatorType, validatorTemplate) { + if (validators[validatorType] === undefined) { + validators[validatorType] = []; + } + if (!foundValidator && $this.data("validation" + el + formatValidatorName(validatorTemplate.name)) !== undefined) { + validators[validatorType].push( + $.extend( + true, { + name: formatValidatorName(validatorTemplate.name), + message: message + }, + validatorTemplate.init($this, el) + ) + ); + foundValidator = true; + } + } + ); + + if (!foundValidator && settings.builtInValidators[el.toLowerCase()]) { + + var validator = $.extend(true, {}, settings.builtInValidators[el.toLowerCase()]); + if (hasOverrideMessage) { + validator.message = message; + } + var validatorType = validator.type.toLowerCase(); + + if (validatorType === "shortcut") { + foundValidator = true; + } else { + $.each( + settings.validatorTypes, + function(validatorTemplateType, validatorTemplate) { + if (validators[validatorTemplateType] === undefined) { + validators[validatorTemplateType] = []; + } + if (!foundValidator && validatorType === validatorTemplateType.toLowerCase()) { + $this.data("validation" + el + formatValidatorName(validatorTemplate.name), validator[validatorTemplate.name.toLowerCase()]); + validators[validatorType].push( + $.extend( + validator, + validatorTemplate.init($this, el) + ) + ); + foundValidator = true; + } + } + ); + } + } + + if (!foundValidator) { + $.error("Cannot find validation info for '" + el + "'"); + } + }); + + // ============================================================= + // STORE FALLBACK VALUES + // ============================================================= + + $helpBlock.data( + "original-contents", + ( + $helpBlock.data("original-contents") ? + $helpBlock.data("original-contents") : + $helpBlock.html() + ) + ); + + $helpBlock.data( + "original-role", + ( + $helpBlock.data("original-role") ? + $helpBlock.data("original-role") : + $helpBlock.attr("role") + ) + ); + + $controlGroup.data( + "original-classes", + ( + $controlGroup.data("original-clases") ? + $controlGroup.data("original-classes") : + $controlGroup.attr("class") + ) + ); + + $this.data( + "original-aria-invalid", + ( + $this.data("original-aria-invalid") ? + $this.data("original-aria-invalid") : + $this.attr("aria-invalid") + ) + ); + + // ============================================================= + // VALIDATION + // ============================================================= + + $this.bind( + "validation.validation", + function(event, params) { + + var value = getValue($this); + + // Get a list of the errors to apply + var errorsFound = []; + + $.each(validators, function(validatorType, validatorTypeArray) { + if (value || value.length || (params && params.includeEmpty) || (!!settings.validatorTypes[validatorType].blockSubmit && params && !!params.submitting)) { + $.each(validatorTypeArray, function(i, validator) { + if (settings.validatorTypes[validatorType].validate($this, value, validator)) { + errorsFound.push(validator.message); + } + }); + } + }); + + return errorsFound; + } + ); + + $this.bind( + "getValidators.validation", + function() { + return validators; + } + ); + + // ============================================================= + // WATCH FOR CHANGES + // ============================================================= + $this.bind( + "submit.validation", + function() { + return $this.triggerHandler("change.validation", { + submitting: true + }); + } + ); + $this.bind( + [ + "keyup", + "focus", + "blur", + "click", + "keydown", + "keypress", + "change" + ].join(".validation ") + ".validation", + function(e, params) { + + var value = getValue($this); + + var errorsFound = []; + + $controlGroup.find("input,textarea,select").each(function(i, el) { + var oldCount = errorsFound.length; + $.each($(el).triggerHandler("validation.validation", params), function(j, message) { + errorsFound.push(message); + }); + if (errorsFound.length > oldCount) { + $(el).attr("aria-invalid", "true"); + } else { + var original = $this.data("original-aria-invalid"); + $(el).attr("aria-invalid", (original !== undefined ? original : false)); + } + }); + + $form.find("input,select,textarea").not($this).not("[name=\"" + $this.attr("name") + "\"]").trigger("validationLostFocus.validation"); + + errorsFound = $.unique(errorsFound.sort()); + + // Were there any errors? + if (errorsFound.length) { + // Better flag it up as a warning. + $controlGroup.removeClass("success error").addClass("warning"); + + // How many errors did we find? + if (settings.options.semanticallyStrict && errorsFound.length === 1) { + // Only one? Being strict? Just output it. + $helpBlock.html(errorsFound[0] + + (settings.options.prependExistingHelpBlock ? $helpBlock.data("original-contents") : "")); + } else { + // Multiple? Being sloppy? Glue them together into an UL. + $helpBlock.html("
  • " + errorsFound.join("
  • ") + "
" + + (settings.options.prependExistingHelpBlock ? $helpBlock.data("original-contents") : "")); + } + } else { + $controlGroup.removeClass("warning error success"); + if (value.length > 0) { + $controlGroup.addClass("success"); + } + $helpBlock.html($helpBlock.data("original-contents")); + } + + if (e.type === "blur") { + $controlGroup.removeClass("success"); + } + } + ); + $this.bind("validationLostFocus.validation", function() { + $controlGroup.removeClass("success"); + }); + }); + }, + destroy: function() { + + return this.each( + function() { + + var + $this = $(this), + $controlGroup = $this.parents(".form-group").first(), + $helpBlock = $controlGroup.find(".help-block").first(); + + // remove our events + $this.unbind('.validation'); // events are namespaced. + // reset help text + $helpBlock.html($helpBlock.data("original-contents")); + // reset classes + $controlGroup.attr("class", $controlGroup.data("original-classes")); + // reset aria + $this.attr("aria-invalid", $this.data("original-aria-invalid")); + // reset role + $helpBlock.attr("role", $this.data("original-role")); + // remove all elements we created + if (createdElements.indexOf($helpBlock[0]) > -1) { + $helpBlock.remove(); + } + + } + ); + + }, + collectErrors: function(includeEmpty) { + + var errorMessages = {}; + this.each(function(i, el) { + var $el = $(el); + var name = $el.attr("name"); + var errors = $el.triggerHandler("validation.validation", { + includeEmpty: true + }); + errorMessages[name] = $.extend(true, errors, errorMessages[name]); + }); + + $.each(errorMessages, function(i, el) { + if (el.length === 0) { + delete errorMessages[i]; + } + }); + + return errorMessages; + + }, + hasErrors: function() { + + var errorMessages = []; + + this.each(function(i, el) { + errorMessages = errorMessages.concat( + $(el).triggerHandler("getValidators.validation") ? $(el).triggerHandler("validation.validation", { + submitting: true + }) : [] + ); + }); + + return (errorMessages.length > 0); + }, + override: function(newDefaults) { + defaults = $.extend(true, defaults, newDefaults); + } + }, + validatorTypes: { + callback: { + name: "callback", + init: function($this, name) { + return { + validatorName: name, + callback: $this.data("validation" + name + "Callback"), + lastValue: $this.val(), + lastValid: true, + lastFinished: true + }; + }, + validate: function($this, value, validator) { + if (validator.lastValue === value && validator.lastFinished) { + return !validator.lastValid; + } + + if (validator.lastFinished === true) { + validator.lastValue = value; + validator.lastValid = true; + validator.lastFinished = false; + + var rrjqbvValidator = validator; + var rrjqbvThis = $this; + executeFunctionByName( + validator.callback, + window, + $this, + value, + function(data) { + if (rrjqbvValidator.lastValue === data.value) { + rrjqbvValidator.lastValid = data.valid; + if (data.message) { + rrjqbvValidator.message = data.message; + } + rrjqbvValidator.lastFinished = true; + rrjqbvThis.data("validation" + rrjqbvValidator.validatorName + "Message", rrjqbvValidator.message); + // Timeout is set to avoid problems with the events being considered 'already fired' + setTimeout(function() { + rrjqbvThis.trigger("change.validation"); + }, 1); // doesn't need a long timeout, just long enough for the event bubble to burst + } + } + ); + } + + return false; + + } + }, + ajax: { + name: "ajax", + init: function($this, name) { + return { + validatorName: name, + url: $this.data("validation" + name + "Ajax"), + lastValue: $this.val(), + lastValid: true, + lastFinished: true + }; + }, + validate: function($this, value, validator) { + if ("" + validator.lastValue === "" + value && validator.lastFinished === true) { + return validator.lastValid === false; + } + + if (validator.lastFinished === true) { + validator.lastValue = value; + validator.lastValid = true; + validator.lastFinished = false; + $.ajax({ + url: validator.url, + data: "value=" + value + "&field=" + $this.attr("name"), + dataType: "json", + success: function(data) { + if ("" + validator.lastValue === "" + data.value) { + validator.lastValid = !!(data.valid); + if (data.message) { + validator.message = data.message; + } + validator.lastFinished = true; + $this.data("validation" + validator.validatorName + "Message", validator.message); + // Timeout is set to avoid problems with the events being considered 'already fired' + setTimeout(function() { + $this.trigger("change.validation"); + }, 1); // doesn't need a long timeout, just long enough for the event bubble to burst + } + }, + failure: function() { + validator.lastValid = true; + validator.message = "ajax call failed"; + validator.lastFinished = true; + $this.data("validation" + validator.validatorName + "Message", validator.message); + // Timeout is set to avoid problems with the events being considered 'already fired' + setTimeout(function() { + $this.trigger("change.validation"); + }, 1); // doesn't need a long timeout, just long enough for the event bubble to burst + } + }); + } + + return false; + + } + }, + regex: { + name: "regex", + init: function($this, name) { + return { + regex: regexFromString($this.data("validation" + name + "Regex")) + }; + }, + validate: function($this, value, validator) { + return (!validator.regex.test(value) && !validator.negative) || + (validator.regex.test(value) && validator.negative); + } + }, + required: { + name: "required", + init: function($this, name) { + return {}; + }, + validate: function($this, value, validator) { + return !!(value.length === 0 && !validator.negative) || + !!(value.length > 0 && validator.negative); + }, + blockSubmit: true + }, + match: { + name: "match", + init: function($this, name) { + var element = $this.parents("form").first().find("[name=\"" + $this.data("validation" + name + "Match") + "\"]").first(); + element.bind("validation.validation", function() { + $this.trigger("change.validation", { + submitting: true + }); + }); + return { + "element": element + }; + }, + validate: function($this, value, validator) { + return (value !== validator.element.val() && !validator.negative) || + (value === validator.element.val() && validator.negative); + }, + blockSubmit: true + }, + max: { + name: "max", + init: function($this, name) { + return { + max: $this.data("validation" + name + "Max") + }; + }, + validate: function($this, value, validator) { + return (parseFloat(value, 10) > parseFloat(validator.max, 10) && !validator.negative) || + (parseFloat(value, 10) <= parseFloat(validator.max, 10) && validator.negative); + } + }, + min: { + name: "min", + init: function($this, name) { + return { + min: $this.data("validation" + name + "Min") + }; + }, + validate: function($this, value, validator) { + return (parseFloat(value) < parseFloat(validator.min) && !validator.negative) || + (parseFloat(value) >= parseFloat(validator.min) && validator.negative); + } + }, + maxlength: { + name: "maxlength", + init: function($this, name) { + return { + maxlength: $this.data("validation" + name + "Maxlength") + }; + }, + validate: function($this, value, validator) { + return ((value.length > validator.maxlength) && !validator.negative) || + ((value.length <= validator.maxlength) && validator.negative); + } + }, + minlength: { + name: "minlength", + init: function($this, name) { + return { + minlength: $this.data("validation" + name + "Minlength") + }; + }, + validate: function($this, value, validator) { + return ((value.length < validator.minlength) && !validator.negative) || + ((value.length >= validator.minlength) && validator.negative); + } + }, + maxchecked: { + name: "maxchecked", + init: function($this, name) { + var elements = $this.parents("form").first().find("[name=\"" + $this.attr("name") + "\"]"); + elements.bind("click.validation", function() { + $this.trigger("change.validation", { + includeEmpty: true + }); + }); + return { + maxchecked: $this.data("validation" + name + "Maxchecked"), + elements: elements + }; + }, + validate: function($this, value, validator) { + return (validator.elements.filter(":checked").length > validator.maxchecked && !validator.negative) || + (validator.elements.filter(":checked").length <= validator.maxchecked && validator.negative); + }, + blockSubmit: true + }, + minchecked: { + name: "minchecked", + init: function($this, name) { + var elements = $this.parents("form").first().find("[name=\"" + $this.attr("name") + "\"]"); + elements.bind("click.validation", function() { + $this.trigger("change.validation", { + includeEmpty: true + }); + }); + return { + minchecked: $this.data("validation" + name + "Minchecked"), + elements: elements + }; + }, + validate: function($this, value, validator) { + return (validator.elements.filter(":checked").length < validator.minchecked && !validator.negative) || + (validator.elements.filter(":checked").length >= validator.minchecked && validator.negative); + }, + blockSubmit: true + } + }, + builtInValidators: { + email: { + name: "Email", + type: "shortcut", + shortcut: "validemail" + }, + validemail: { + name: "Validemail", + type: "regex", + regex: "[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\\.[A-Za-z]{2,4}", + message: "Not a valid email address" + }, + passwordagain: { + name: "Passwordagain", + type: "match", + match: "password", + message: "Does not match the given password" + }, + positive: { + name: "Positive", + type: "shortcut", + shortcut: "number,positivenumber" + }, + negative: { + name: "Negative", + type: "shortcut", + shortcut: "number,negativenumber" + }, + number: { + name: "Number", + type: "regex", + regex: "([+-]?\\\d+(\\\.\\\d*)?([eE][+-]?[0-9]+)?)?", + message: "Must be a number" + }, + integer: { + name: "Integer", + type: "regex", + regex: "[+-]?\\\d+", + message: "No decimal places allowed" + }, + positivenumber: { + name: "Positivenumber", + type: "min", + min: 0, + message: "Must be a positive number" + }, + negativenumber: { + name: "Negativenumber", + type: "max", + max: 0, + message: "Must be a negative number" + }, + required: { + name: "Required", + type: "required", + message: "This is required" + }, + checkone: { + name: "Checkone", + type: "minchecked", + minchecked: 1, + message: "Check at least one option" + } + } + }; + + var formatValidatorName = function(name) { + return name + .toLowerCase() + .replace( + /(^|\s)([a-z])/g, + function(m, p1, p2) { + return p1 + p2.toUpperCase(); + } + ); + }; + + var getValue = function($this) { + // Extract the value we're talking about + var value = $this.val(); + var type = $this.attr("type"); + if (type === "checkbox") { + value = ($this.is(":checked") ? value : ""); + } + if (type === "radio") { + value = ($('input[name="' + $this.attr("name") + '"]:checked').length > 0 ? value : ""); + } + return value; + }; + + function regexFromString(inputstring) { + return new RegExp("^" + inputstring + "$"); + } + + /** + * Thanks to Jason Bunting via StackOverflow.com + * + * http://stackoverflow.com/questions/359788/how-to-execute-a-javascript-function-when-i-have-its-name-as-a-string#answer-359910 + * Short link: http://tinyurl.com/executeFunctionByName + **/ + function executeFunctionByName(functionName, context /*, args*/ ) { + var args = Array.prototype.slice.call(arguments).splice(2); + var namespaces = functionName.split("."); + var func = namespaces.pop(); + for (var i = 0; i < namespaces.length; i++) { + context = context[namespaces[i]]; + } + return context[func].apply(this, args); + } + + $.fn.jqBootstrapValidation = function(method) { + + if (defaults.methods[method]) { + return defaults.methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); + } else if (typeof method === 'object' || !method) { + return defaults.methods.init.apply(this, arguments); + } else { + $.error('Method ' + method + ' does not exist on jQuery.jqBootstrapValidation'); + return null; + } + + }; + + $.jqBootstrapValidation = function(options) { + $(":input").not("[type=image],[type=submit]").jqBootstrapValidation.apply(this, arguments); + }; + +})(jQuery); diff --git a/src/Public/scss/_bootstrap-overrides.scss b/src/Public/scss/_bootstrap-overrides.scss new file mode 100644 index 0000000..4520b29 --- /dev/null +++ b/src/Public/scss/_bootstrap-overrides.scss @@ -0,0 +1,27 @@ +// Bootstrap overrides for this template +.btn { + font-size: 14px; + font-weight: 800; + padding: 15px 25px; + letter-spacing: 1px; + text-transform: uppercase; + border-radius: 0; + @include sans-serif-font; +} + +.btn-primary { + background-color: $primary; + border-color: $primary; + &:hover, + &:focus, + &:active { + color: $white; + background-color: darken($primary, 7.5) !important; + border-color: darken($primary, 7.5) !important; + } +} + +.btn-lg { + font-size: 16px; + padding: 25px 35px; +} diff --git a/src/Public/scss/_contact.scss b/src/Public/scss/_contact.scss new file mode 100644 index 0000000..99ac0a5 --- /dev/null +++ b/src/Public/scss/_contact.scss @@ -0,0 +1,58 @@ +// Styling for the contact page +.floating-label-form-group { + font-size: 14px; + position: relative; + margin-bottom: 0; + padding-bottom: 0.5em; + border-bottom: 1px solid $gray-300; + input, + textarea { + font-size: 1.5em; + position: relative; + z-index: 1; + padding: 0; + resize: none; + border: none; + border-radius: 0; + background: none; + box-shadow: none !important; + @include serif-font; + &::-webkit-input-placeholder { + color: $gray-600; + @include serif-font; + } + } + label { + font-size: 0.85em; + line-height: 1.764705882em; + position: relative; + z-index: 0; + top: 2em; + display: block; + margin: 0; + -webkit-transition: top 0.3s ease, opacity 0.3s ease; + -moz-transition: top 0.3s ease, opacity 0.3s ease; + -ms-transition: top 0.3s ease, opacity 0.3s ease; + transition: top 0.3s ease, opacity 0.3s ease; + opacity: 0; + } + .help-block { + margin: 15px 0; + } +} + +.floating-label-form-group-with-value { + label { + top: 0; + opacity: 1; + } +} + +.floating-label-form-group-with-focus { + label { + color: $primary; + } +} +form .form-group:first-child .floating-label-form-group { + border-top: 1px solid $gray-300; +} diff --git a/src/Public/scss/_footer.scss b/src/Public/scss/_footer.scss new file mode 100644 index 0000000..8b463d8 --- /dev/null +++ b/src/Public/scss/_footer.scss @@ -0,0 +1,13 @@ +// Styling for the footer +footer { + padding: 50px 0 65px; + .list-inline { + margin: 0; + padding: 0; + } + .copyright { + font-size: 14px; + margin-bottom: 0; + text-align: center; + } +} diff --git a/src/Public/scss/_global.scss b/src/Public/scss/_global.scss new file mode 100644 index 0000000..b6ac96e --- /dev/null +++ b/src/Public/scss/_global.scss @@ -0,0 +1,77 @@ +// Global styling for this template +body { + font-size: 20px; + color: $gray-900; + @include serif-font; +} + +p { + line-height: 1.5; + margin: 30px 0; + a { + text-decoration: underline; + } +} + +h1, +h2, +h3, +h4, +h5, +h6 { + font-weight: 800; + @include sans-serif-font; +} + +a { + color: $gray-900; + @include transition-all; + &:focus, + &:hover { + color: $primary; + } +} + +blockquote { + font-style: italic; + color: $gray-600; +} + +.section-heading { + font-size: 36px; + font-weight: 700; + margin-top: 60px; +} + +.caption { + font-size: 14px; + font-style: italic; + display: block; + margin: 0; + padding: 10px; + text-align: center; + border-bottom-right-radius: 5px; + border-bottom-left-radius: 5px; +} + +::-moz-selection { + color: $white; + background: $primary; + text-shadow: none; +} + +::selection { + color: $white; + background: $primary; + text-shadow: none; +} + +img::selection { + color: $white; + background: transparent; +} + +img::-moz-selection { + color: $white; + background: transparent; +} diff --git a/src/Public/scss/_masthead.scss b/src/Public/scss/_masthead.scss new file mode 100644 index 0000000..283f063 --- /dev/null +++ b/src/Public/scss/_masthead.scss @@ -0,0 +1,82 @@ +// Styling for the masthead +header.masthead { + // TIP: Background images are set within the HTML using inline CSS! + margin-bottom: 50px; + background: no-repeat center center; + background-color: $gray-600; + background-attachment: scroll; + position: relative; + @include background-cover; + .overlay { + position: absolute; + top: 0; + left: 0; + height: 100%; + width: 100%; + background-color: $gray-900; + opacity: 0.5; + } + .page-heading, + .post-heading, + .site-heading { + padding: 200px 0 150px; + color: white; + @media only screen and (min-width: 768px) { + padding: 200px 0; + } + } + .page-heading, + .site-heading { + text-align: center; + h1 { + font-size: 50px; + margin-top: 0; + } + .subheading { + font-size: 24px; + font-weight: 300; + line-height: 1.1; + display: block; + margin: 10px 0 0; + @include sans-serif-font; + } + @media only screen and (min-width: 768px) { + h1 { + font-size: 80px; + } + } + } + .post-heading { + h1 { + font-size: 35px; + } + .meta, + .subheading { + line-height: 1.1; + display: block; + } + .subheading { + font-size: 24px; + font-weight: 600; + margin: 10px 0 30px; + @include sans-serif-font; + } + .meta { + font-size: 20px; + font-weight: 300; + font-style: italic; + @include serif-font; + a { + color: $white; + } + } + @media only screen and (min-width: 768px) { + h1 { + font-size: 55px; + } + .subheading { + font-size: 30px; + } + } + } +} diff --git a/src/Public/scss/_mixins.scss b/src/Public/scss/_mixins.scss new file mode 100644 index 0000000..ca7464a --- /dev/null +++ b/src/Public/scss/_mixins.scss @@ -0,0 +1,68 @@ +// Mixins +// Bootstrap Button Variant +@mixin button-variant($color, $background, $border) { + color: $color; + border-color: $border; + background-color: $background; + &.focus, + &:focus { + color: $color; + border-color: darken($border, 25%); + background-color: darken($background, 10%); + } + &:hover { + color: $color; + border-color: darken($border, 12%); + background-color: darken($background, 10%); + } + &.active, + &:active, + .open > &.dropdown-toggle { + color: $color; + border-color: darken($border, 12%); + background-color: darken($background, 10%); + &.focus, + &:focus, + &:hover { + color: $color; + border-color: darken($border, 25%); + background-color: darken($background, 17%); + } + } + &.active, + &:active, + .open > &.dropdown-toggle { + background-image: none; + } + &.disabled, + &[disabled], + fieldset[disabled] & { + &.focus, + &:focus, + &:hover { + border-color: $border; + background-color: $background; + } + } + .badge { + color: $background; + background-color: $color; + } +} +@mixin transition-all() { + -webkit-transition: all 0.2s; + -moz-transition: all 0.2s; + transition: all 0.2s; +} +@mixin background-cover() { + -webkit-background-size: cover; + -moz-background-size: cover; + -o-background-size: cover; + background-size: cover; +} +@mixin serif-font() { + font-family: 'Lora', 'Times New Roman', serif; +} +@mixin sans-serif-font() { + font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; +} diff --git a/src/Public/scss/_navbar.scss b/src/Public/scss/_navbar.scss new file mode 100644 index 0000000..92d9893 --- /dev/null +++ b/src/Public/scss/_navbar.scss @@ -0,0 +1,100 @@ +// Styling for the navbar +#mainNav { + position: absolute; + border-bottom: 1px solid $gray-200; + background-color: white; + @include sans-serif-font; + .navbar-brand { + font-weight: 800; + color: $gray-800; + } + .navbar-toggler { + font-size: 12px; + font-weight: 800; + padding: 13px; + text-transform: uppercase; + color: $gray-800; + } + .navbar-nav { + > li.nav-item { + > a { + font-size: 12px; + font-weight: 800; + letter-spacing: 1px; + text-transform: uppercase; + } + } + } + @media only screen and (min-width: 992px) { + border-bottom: 1px solid transparent; + background: transparent; + .navbar-brand { + padding: 10px 20px; + color: $white; + &:focus, + &:hover { + color: fade-out($white, .2); + } + } + .navbar-nav { + > li.nav-item { + > a { + padding: 10px 20px; + color: $white; + &:focus, + &:hover { + color: fade-out($white, .2); + } + } + } + } + } + @media only screen and (min-width: 992px) { + -webkit-transition: background-color 0.2s; + -moz-transition: background-color 0.2s; + transition: background-color 0.2s; + /* Force Hardware Acceleration in WebKit */ + -webkit-transform: translate3d(0, 0, 0); + -moz-transform: translate3d(0, 0, 0); + -ms-transform: translate3d(0, 0, 0); + -o-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + -webkit-backface-visibility: hidden; + &.is-fixed { + /* when the user scrolls down, we hide the header right above the viewport */ + position: fixed; + top: -67px; + -webkit-transition: -webkit-transform 0.2s; + -moz-transition: -moz-transform 0.2s; + transition: transform 0.2s; + border-bottom: 1px solid darken($white, .05); + background-color: fade-out($white, .1); + .navbar-brand { + color: $gray-900; + &:focus, + &:hover { + color: $primary; + } + } + .navbar-nav { + > li.nav-item { + > a { + color: $gray-900; + &:focus, + &:hover { + color: $primary; + } + } + } + } + } + &.is-visible { + /* if the user changes the scrolling direction, we show the header */ + -webkit-transform: translate3d(0, 100%, 0); + -moz-transform: translate3d(0, 100%, 0); + -ms-transform: translate3d(0, 100%, 0); + -o-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + } + } +} diff --git a/src/Public/scss/_post.scss b/src/Public/scss/_post.scss new file mode 100644 index 0000000..5310cfe --- /dev/null +++ b/src/Public/scss/_post.scss @@ -0,0 +1,42 @@ +// Styling for the post page +.post-preview { + > a { + color: $gray-900; + &:focus, + &:hover { + text-decoration: none; + color: $primary; + } + > .post-title { + font-size: 30px; + margin-top: 30px; + margin-bottom: 10px; + } + > .post-subtitle { + font-weight: 300; + margin: 0 0 10px; + } + } + > .post-meta { + font-size: 18px; + font-style: italic; + margin-top: 0; + color: $gray-600; + > a { + text-decoration: none; + color: $gray-900; + &:focus, + &:hover { + text-decoration: underline; + color: $primary; + } + } + } + @media only screen and (min-width: 768px) { + > a { + > .post-title { + font-size: 36px; + } + } + } +} diff --git a/src/Public/scss/_variables.scss b/src/Public/scss/_variables.scss new file mode 100644 index 0000000..e3452cf --- /dev/null +++ b/src/Public/scss/_variables.scss @@ -0,0 +1,33 @@ +// Variables + +$white: #fff !default; +$gray-100: #f8f9fa !default; +$gray-200: #e9ecef !default; +$gray-300: #dee2e6 !default; +$gray-400: #ced4da !default; +$gray-500: #adb5bd !default; +$gray-600: #868e96 !default; +$gray-700: #495057 !default; +$gray-800: #343a40 !default; +$gray-900: #212529 !default; +$black: #000 !default; + +$blue: #007bff !default; +$indigo: #6610f2 !default; +$purple: #6f42c1 !default; +$pink: #e83e8c !default; +$red: #dc3545 !default; +$orange: #fd7e14 !default; +$yellow: #ffc107 !default; +$green: #28a745 !default; +$teal: #0085A1 !default; +$cyan: #17a2b8 !default; + +$primary: $teal !default; +$secondary: $gray-600 !default; +$success: $green !default; +$info: $cyan !default; +$warning: $yellow !default; +$danger: $red !default; +$light: $gray-100 !default; +$dark: $gray-800 !default; diff --git a/src/Public/scss/clean-blog.scss b/src/Public/scss/clean-blog.scss new file mode 100644 index 0000000..681d6af --- /dev/null +++ b/src/Public/scss/clean-blog.scss @@ -0,0 +1,9 @@ +@import "variables"; +@import "mixins"; +@import "global"; +@import "navbar"; +@import "masthead"; +@import "post"; +@import "contact"; +@import "footer"; +@import "bootstrap-overrides"; diff --git a/src/Router/Route.php b/src/Router/Route.php new file mode 100644 index 0000000..aeaafd6 --- /dev/null +++ b/src/Router/Route.php @@ -0,0 +1,71 @@ +path = trim($path, '/'); + $this->callable = $callable; + } + + public function with( $param, $regex) : string + { + $this->params[$param] = str_replace('(','(?:', $regex); + return $this; + } + + public function match(string $url) : bool + { + $url = trim($url, '/'); + $path = preg_replace_callback('#:([\w]+)#', [$this, 'paramMatch'], $this->path); + $regex = "#^$path$#i"; + if (!preg_match($regex, $url, $matches)) + { + return false; + } + array_shift($matches); + $this->matches = $matches; + return true; + } + + private function paramMatch(string $match) : string + { + if (isset($this->params[$match[1]])) + { + return '('.$this->params[$match[1]].')'; + } + return '([^/]+)'; + } + + public function call() + { + if (is_string($this->callable)) + { + $params = explode('#', $this->callable); + $controller = "App\\Controller\\" . $params[0] . "Controller"; + $controller = new $controller(); + return call_user_func_array([$controller, $params[1]], $this->matches); + } + else + { + return call_user_func_array($this->callable, $this->matches); + } + } + + public function getUrl(array $params) : string + { + $path = $this->path; + foreach ($params as $k => $v) + { + $path = str_replace(":$k", $v, $path); + } + return $path; + } +} diff --git a/src/Router/Router.php b/src/Router/Router.php new file mode 100644 index 0000000..4628995 --- /dev/null +++ b/src/Router/Router.php @@ -0,0 +1,59 @@ +url = $url; + } + + public function get(string $path, string $callable, $name = null) + { + return $this->add($path, $callable, $name, 'GET'); + } + + public function post(string $path, string $callable, $name = null) + { + return $this->add($path, $callable, $name, 'POST'); + } + + private function add(string $path, string $callable, $name, string $method) + { + $route = new Route($path, $callable); + $this->routes[$method][] = $route; + if (is_string($callable) && $name === null) { + $name = $callable; + } + if ($name) { + $this->namedRoutes[$name] = $route; + } + return $route; + } + + public function run() + { + if (!isset($this->routes[$_SERVER['REQUEST_METHOD']])) { + throw new RouterException('REQUEST_METHOD does not exist'); + } + foreach ($this->routes[$_SERVER['REQUEST_METHOD']] as $route) { + if ($route->match($this->url)) { + return $route->call(); + } + } + throw new RouterException('No matching routes'); + } + + public function url(string $name, array $params = []) : array + { + if (!isset($this->namedRoutes[$name])) { + throw new RouterException('No route matches this name'); + } + return $this->namedRoutes[$name]->getUrl($params); + } +} diff --git a/src/Router/RouterException.php b/src/Router/RouterException.php new file mode 100644 index 0000000..d1dff85 --- /dev/null +++ b/src/Router/RouterException.php @@ -0,0 +1,8 @@ + + + + + + + + + + + + + {% block title %}{% endblock %} + {% block stylesheet %}{% endblock %} + + + + {% include 'navbar.html.twig' %} + {% block content %} {% endblock %} + {% block javascript %} {% endblock %} + + + diff --git a/src/View/home.html.twig b/src/View/home.html.twig new file mode 100644 index 0000000..117026a --- /dev/null +++ b/src/View/home.html.twig @@ -0,0 +1,65 @@ +{% extends 'base.html.twig' %} + +{% block title %} + Accueil +{% endblock %} + +{% block content %} +
+
+
+
+
+
+

Mon Blog

+ Un blog réalisé avec PHP et Twig +
+
+
+
+
+
+
+
+ Logo Home +
+
+

Mathias Micheli, développeur en alternance.

+ Télécharger mon CV → +

+ +
+
+
+{% endblock %} + +{% block javascript %} + + + +{% endblock %} diff --git a/src/View/navbar.html.twig b/src/View/navbar.html.twig new file mode 100644 index 0000000..f6c11e8 --- /dev/null +++ b/src/View/navbar.html.twig @@ -0,0 +1,24 @@ +