ТОП-13 крутых идей веб-проектов для прокачки навыков
Иногда хочется написать что-то эдакое, а идей нет. Вот вам подборка классных идей для веб-проектов на любой случай и вкус.
Данные приложения поспособствуют:
- поднятию уровня знаний по программированию;
- изучению новых технологий;
- созданию нового проекта в портфолио;
- созданию продукта, который можно легко расширить доп. возможностями.
Список идей разделен на три базовых уровня сложности:
- Beginner – разработчики на ранних этапах своего учебного путешествия, которые обычно сосредоточены на создании простых веб-проектов.
- Intermediate – разработчики с определенным количеством опыта. Они себя хорошо чувствуют в создании UI/UX, в использовании инструментов разработки и написании приложений, использующих API.
- Advanced – программисты, имеющие все вышеперечисленное и создающие более продвинутые штуки, такие как бэкенд и службы БД.
1. Блокнот
Уровень: beginner
Описание: создание и сохранение заметок.
Возможности:
- сохранение;
- редактирование;
- удаление;
- при закрытии окна / браузера данные должны сохраняться, а при "возвращении" пользователя – динамически подгружаться.
Бонус:
- пользователь может редактировать заметку в Markdown-формате; при сохранении результат конвертируется в HTML;
- пользователю видна дата последнего изменения заметки.
Полезные ссылки:
Пример:
HTML
<h1 class="header">AngularJS Markdown Notes</h1> <div ng-app="notesApp" class="app"> <div ng-controller="notesController"> <span class="icons"> <i class="fa fa-plus-circle fa-2x" ng-click="addNote()"></i> </span> <div class="note" ng-repeat="note in notes | orderBy:'createdOn':true track by $index"> <span class="date">{{note.createdOn | date:"EEEE dd MMMM, yyyy 'at' h:mma"}}</span> <span class="icons"> <i class="fa fa-check fa-lg" ng-if="note.edit == false"></i> <i class="fa fa-pencil fa-lg" ng-if="note.edit == true"></i> <i class="fa fa-trash-o fa-lg" ng-click="delete(notes.length - $index - 1)"></i></span> <div class="markdown-body" ng-model="note.text" ng-change="update(notes.length - $index - 1,note.text)" ng-focus="note.edit = true" ng-blur="note.edit = false" markdown-editable contenteditable="true">{{ note.text }}</div> </div> </div> </div>
CSS
@import url(https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,700); @import url(https://fonts.googleapis.com/css?family=Source+Code+Pro); * { box-sizing:border-box; } body { max-width:580px; margin:auto; padding:10px; position:relative; background: #fdfdfd; } img { max-width:100%; height:auto; } div[contenteditable] { font-size:1.2em; font-family: 'Source Sans Pro', sans-serif; border:none; box-shadow:none; margin:0; width:100%; padding:10px 20px 10px; min-height:300px; background:#f9f9f9; pre, code { background:#ededed; } } div[contenteditable]:focus{ font-family: 'Source Code Pro', courier, monospace; font-size:1em; } .header { margin:5px 10px 0; color:#d46e58; display:inline-block; } .note { background: #eee; box-shadow:#999 1px 1px 3px; margin:30px 0; padding-top:40px; min-height:200px; width:100%; display:block; position:relative; overflow:hidden; } .date { position:absolute; top:0; left:0; padding:15px; font-size:0.7em; font-style: italic; color:#71CBD0; } .icons { position:absolute; right:0; top:0; padding:10px; } .fa-trash-o, .fa-plus-circle { cursor:pointer; } .fa-check { color:#92D788; } .fa-trash-o { color:#C2474B; &:hover { color:darken(#C2474B,20); } } .fa-pencil { color:#DBC394; } .fa-plus-circle { color:#71CBD0; &:hover { color:darken(#71CBD0,20); } }
JS
(function(){ var app = angular.module('notesApp',['angular-markdown-editable']); app.controller('notesController', function($scope){ // Initial Data $scope.notes = [{ createdOn:1428237500771, edit:false, text:"#Hello, World!\n\n Delete this note\n\nMarkdown compiled using the fantastic [angular-markdown-editable](http://projects.quiver. is/angular-markdown-editable/) directive." }]; // Add New Note $scope.addNote = function(){ $scope.newNote = {}; $scope.newNote.createdOn = Date.now(); $scope.newNote.text = ' '; $scope.newNote.edit = true; $scope.notes.push($scope.newNote); $scope.newNote = {}; }; // Delete Note $scope.delete = function (i) { var r = confirm("Are you sure you want to delete this note?"); if (r == true) $scope.notes.splice(i, 1); }; // Update Note $scope.update = function(i, note) { $scope.notes[i].text = note; $scope.notes[i].edit = false; }; // End Controller }); // End Function })();
2. Рождественские огни
Уровень: beginner
Описание: ваша задача нарисовать в ряд семь цветных окружностей и по таймеру изменять интенсивность свечения каждого из них. После прохода нескольких рядов "лампочек" последовательность подсвечивания меняется.
Возможности:
- пользователь может нажать кнопку для включения/выключения огней;
- пользователь может изменить время между интервалами свечения.
Бонус:
- выбор цвета заполнения каждой окружности;
- указание значения интенсивности;
- изменение размера любого круга в ряду;
- указание количества строк (от 1 до 7).
Пример:
HTML
<ul class="lightrope"> <li></li> <li></li> <li></li> *** *** <li></li> <li></li> </ul>
CSS
$globe-width: 12px; $globe-height: 28px; $globe-spacing: 40px; $globe-spread: 3px; $light-off-opacity: 0.4; body { background: #000; } .lightrope { text-align: center; white-space: nowrap; overflow: hidden; position: absolute; z-index: 1; margin: -15px 0 0 0; padding: 0; pointer-events: none; width: 100%; li { position: relative; animation-fill-mode: both; animation-iteration-count:infinite; list-style: none; margin: 0; padding: 0; display: block; width: $globe-width; height: $globe-height; border-radius: 50%; margin: $globe-spacing/2; display: inline-block; background: rgba(0,247,165,1); box-shadow: 0px $globe-height/6 $globe-width*2 $globe-spread rgba(0,247,165,1); animation-name: flash-1; animation-duration: 2s; &:nth-child(2n+1) { background: rgba(0,255,255,1); box-shadow: 0px $globe-height/6 $globe-width*2 $globe-spread rgba(0,255,255,0.5); animation-name: flash-2; animation-duration: 0.4s; } &:nth-child(4n+2) { background: rgba(247,0,148,1); box-shadow: 0px $globe-height/6 $globe-width*2 $globe-spread rgba(247,0,148,1); animation-name: flash-3; animation-duration: 1.1s; } &:nth-child(odd) { animation-duration: 1.8s; } &:nth-child(3n+1) { animation-duration: 1.4s; } &:before { content: ""; position: absolute; background: #222; width: ($globe-width - 2); height: $globe-height/3; border-radius: 3px; top: (0 - ($globe-height/6)); left: 1px; } &:after { content: ""; top: (0 - $globe-height/2); left: $globe-width - 3; position: absolute; width: $globe-spacing + 12; height: ($globe-height/3 * 2); border-bottom: solid #222 2px; border-radius: 50%; } &:last-child:after { content: none; } &:first-child { margin-left: -$globe-spacing; } } } @keyframes flash-1 { 0%, 100% { background: rgba(0,247,165,1); box-shadow: 0px $globe-height/6 $globe-width*2 $globe-spread rgba(0,247,165,1);} 50% { background: rgba(0,247,165,$light-off-opacity); box-shadow: 0px $globe-height/6 $globe-width*2 $globe-spread rgba(0,247,165,0.2);} } @keyframes flash-2 { 0%, 100% { background: rgba(0,255,255,1); box-shadow: 0px $globe-height/6 $globe-width*2 $globe-spread rgba(0,255,255,1);} 50% { background: rgba(0,255,255,$light-off-opacity); box-shadow: 0px $globe-height/6 $globe-width*2 $globe-spread rgba(0,255,255,0.2);} } @keyframes flash-3 { 0%, 100% { background: rgba(247,0,148,1); box-shadow: 0px $globe-height/6 $globe-width*2 $globe-spread rgba(247,0,148,1);} 50% { background: rgba(247,0,148,$light-off-opacity); box-shadow: 0px $globe-height/6 $globe-width*2 $globe-spread rgba(247,0,148,0.2);} }
Следующий вариант из списка веб-проектов часто приходится реализовывать на практике.
3. Тестирование
Уровень: beginner
Описание: создайте приложение-опрос для тестирования навыков программирования разработчиков.
Возможности:
- запуск теста по кнопке;
- отображение 4 вариантов ответа на вопрос;
- после выбора ответа необходимо показывать следующий вопрос до конца теста;
- в конце пользователю оглашается результат тестирования (общее время прохождения теста, количество правильных ответов, сообщение статуса прохождения теста).
Бонус:
- пользователь может поделиться результатами в социальных сетях;
- выбор нескольких направлений для проверки;
- личный кабинет юзера, в котором хранится информация по всем тестам;
- тест можно проходить несколько раз.
Полезные ссылки:
Пример:
HTML
.quiz-container h1 Quiz interface .question.box p <span>1.</span> What does CSS stand for? ul.answers li.answer.box p <span>a</span> Computer Style Sheets li.answer.box p <span>b</span> Cascading Style Sheets li.answer.box p <span>c</span> Colorful Style Sheets li.answer.box p <span>d</span> Creative Style Sheets button.box Send span ›
CSS
@import url('https://fonts.googleapis.com/css?family=Lato'); $red : #cb2127; $green : #2ecc71; * { box-sizing: border-box; margin: 0; padding: 0; } body, html { height: 100%; } body { font-family: 'Lato', sans-serif; display: flex; flex-direction: column; justify-content: center; &:before { content: ""; display: block; position:fixed; top: 0; left: 0; bottom: 0; right: 0; background-image: url("http://oi66.tinypic.com/23mvj48.jpg"); background-size: cover; background-repeat: no-repeat; filter: blur(2px); z-index: -1; } } .quiz-container{ width: 450px; color: white; font-size: 110%; margin: auto; h1 { text-align: center; color: $red; margin-bottom: 10px; } .box { padding: 10px 15px; } .question { background-color: $red; margin-bottom: 25px; font-size: 115%; padding-top: 20px; padding-bottom: 20px; position: relative; &:after { content: ""; position: absolute; bottom: -15px; left: 35px; width: 0; height: 0; border-left: 15px solid transparent; border-right: 15px solid transparent; border-top: 15px solid $red; } span { background-color: $red; color: white; display: block; float: left; margin-left: -15px; margin-top: -10px; margin-right: 0px; padding: 10px 10px; text-align: center; text-transform: uppercase; width: 40px; } } .answers { padding-left: 0; list-style-type: none; .answer { background-color: #fff; color: $red; margin-bottom: 10px; position: relative; &:hover, &.active{ cursor: pointer; color: $green; span:not([class^="checkmark"]) { background-color: $green; } } &.active span.checkmark { background-color: #fff; position: absolute; top: 8px; right: 20px; font-size: 120%; } span:not([class^="checkmark"]) { background-color: $red; color: white; display: block; float: left; margin-left: -15px; margin-top: - 10px; margin-right: 20px; padding: 10px 0; text-align: center; text-transform: uppercase; width: 40px; } } } button { background-color: $red; color: #fff; font-size: 110%; border: 0; width: 100%; position: relative; &:hover { background-color: $green; cursor:pointer; } span { font-size: 200%; position: absolute; top: -3px; right: 10px; } } } @media (max-width: 450px) { .quiz-container { width: calc(100% - 30px); } }
JS
$(document).ready(function(){ var checkmark = "<span class='checkmark'>✓</span>"; $(".answer").click(function() { $(this).siblings(".answer").removeClass("active").children("span").remove(); $(this).addClass("active").append(checkmark); }); $("button").click(function() { if($(".active").length) { if($(".active").index() === 1) { alert("Well done!"); } else { alert("Wrong answer!"); } } else { alert("Please select an answer!"); } }); });
4. Конвертер римских чисел в десятичные
Уровень: beginner
Описание: римские цифры включают в себя семь символов, каждый с фиксированным целым значением:
- I – 1
- V – 5
- X – 10
- L – 50
- C – 100
- D – 500
- M – 1000
Возможности:
- возможность ввода римских цифр в поле ввода;
- отображение результата в другом поле после нажатия на кнопку;
- если введен неправильный символ, юзер должен увидеть ошибку.
Бонус:
- автоматическое преобразование во время ввода;
- возможность конвертирования в "обе стороны".
Полезные ссылки:
Пример:
5. Поиск книг
Уровень: intermediate
Описание: приложение для поиска книги по запросу (название, автор и т. д.). Список книг отображается со всей информацией о них.
Возможности:
- пользователь может ввести поисковой запрос в поле ввода;
- юзер может отправить запрос, который обращается к API, возвращающей массив с книгами и дополнительной информацией (название, автор, дата публикации и т. д).
Бонус:
- для каждого элемента в списке добавьте ссылку, перенаправляющую на сайт с дополнительной информацией о книге;
- реализовать адаптивный дизайн;
- добавить анимацию загрузки.
Полезные ссылки:
Пример:
HTML
<div id="app"> <form id="book-search" @submit.prevent="search"> <label> <span>Search for a book</span> <input type="text" v-model:trim="searchTerm" placeholder="⌕"> </label> <button type="submit">Search</button> </form> <ol class="search-results"> <li class="search-result" v-for="book in searchResults.items"> <img :src="'http://books.google.com/books/content?id=' + book.id + '&printsec=frontcover&img=1&zoom=1&source=gbs_api'" class="search-result--thumbnail"> <ul class="search-result--info"> <li class="search-result--title">{{ book.volumeInfo.title }}</li> <li v-if="book.volumeInfo.authors" class="search-result--authors"> by {{ bookAuthors(book) }} </li> <li v-if="book.volumeInfo.publishedDate" class="search-result--published"> <span>Published </span> {{ book.volumeInfo.publishedDate }} </li> </ul> </li> </ol> <img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/106403/allthebooks%5B1%5D.jpg" id="all-the-books" v-if="searchResults.items"> </div>
CSS
@function between($to, $from, $toWidth, $fromWidth) { $slope: ($to - $from) / ($toWidth - $fromWidth); $base: $from - $slope * $fromWidth; @return calc(#{$base} + #{100vw * $slope}); } $small: 550px; $large: 1600px; :root { font-size: between(24px, 16px, $large, $small); } *,*:before,*:after { box-sizing: inherit } html { box-sizing: border-box; height: 100%; display: flex; } body { margin: auto; background-color: #faf7f6; color: #805466; } #app { padding: 5%; } #book-search { display: flex; align-items: flex-end; } label span { display: block; font-size: .9rem; font-weight: bold; } input { padding: .2rem .5rem; border-radius: .25rem; border: 2px solid #f3edeb; margin-top: .25rem; margin-right: .25rem; box-shadow: 0 .4rem 1rem -.4rem #d4b1a5; } button { background: rebeccapurple; position: relative; color: white; border: none; padding: 0 .75rem; border-radius: .25rem; font-size: .6rem; line-height: 2.8; text-transform: uppercase; box-shadow: 0 .4rem 1rem -.4rem purple; } .search-results { padding-left: 0; li + li { margin-top: .5rem } } .search-result { background-color: white; position: relative; border: 2px solid #f3edeb; border-radius: .25rem; overflow: hidden; box-shadow: 0 1rem 1rem -.75rem #dfc8c0; display: flex; align-items: center; } .search-result--thumbnail { flex: none; width: 15%; width: 4rem; height: auto; align-self: flex-start; } .search-result--info { display: flex; height: 100%; flex-direction: column; justify-content: space-around; padding: .5rem; list-style: none; } .search-result--title { font-size: 1rem; font-weight: bold; color: #65002a; } .search-result--authors { font-size: .9rem } .search-result--published { font-size: .8rem; } #all-the-books { width: 100%; }
JS
new Vue({ el: '#app', data() { return { searchTerm: '', searchResults: [], } }, methods: { search() { axios.get(`https://www.googleapis.com/books/v1/volumes?q=` + this.searchTerm) .then(response => { this.searchResults = response.data }) .catch(e => { console.log(e) }) }, bookAuthors(book) { let authors = book.volumeInfo.authors; if (authors.length < 3) { authors = authors.join(' and ') } else if (authors.length > 2) { let lastAuthor = ' and ' + authors.slice(-1); authors.pop() authors = authors.join(', ') authors += lastAuthor } return authors } } });
6. Карточная игра
Уровень: intermediate
Описание: эта игра на время, в которой после клика по карточке, появляется изображение. Необходимо найти такую же картинку среди других карточек.
Возможности:
- пользователь видит сетку из n x n карточек, которые изначально скрыты;
- юзер нажимает на кнопку начала игры, и запускается таймер;
- открытая карточка будет отображаться до тех пор, пока пользователь не нажмет на вторую;
- если нашлись две одинаковые карточки, то они исчезают из списка, а если нет – переворачиваются в исходное положение;
- когда нашлись все совпадения – пользователю выводится сообщение.
Бонус:
- юзер может выбирать между несколькими уровнями сложности; увеличение сложности означает: уменьшение времени и/или увеличение количества карточек;
- пользовательская статистика (количество выигрышей/проигрышей, лучшее время).
Пример:
HTML
<div id="g"></div> <div class="logo"> <p class="info">Click the P to get started.</p> <div class="card left"> <div class="flipper"> <div class="f c1">F</div> <div class="b contentbox" id="stats"> <div class="padded"> <h2>Figures</h2> Looks like you haven't FLIPped yet. <a href="javascript:;" class="playnow">Play now</a> </div> </div> </div> </div> <div class="card active twist"> <div class="flipper"> <div class="b f"> <div class="c2">L</div> </div> </div> </div> <div class="card left"> <div class="flipper"> <div class="f c3">I</div> <div class="b contentbox instructions"> <div class="padded"> <h2>Instructions</h2> <p>Press [p] to pause, or [ESC] to abandon game.</p> <p>Flip is a timed card memory game. Click the green cards to see what symbol they uncover and try to find the matching symbol underneath the other cards.</p> <p>Uncover two matching symbols at once to eliminate them from the game.</p> <p>Eliminate all cards as fast as you can to win the game. Have fun FLIPing!</p> </div> </div> </div> </div> <div class="card"> <div class="flipper"> <div class="f c4">P</div> <div class="b contentbox levels"> <a href="javascript:;" data-level="8" class="play">Casual</a> <a href="javascript:;" data-level="18" class="play">Medium</a> <a href="javascript:;" data-level="32" class="play">Hard</a> </div> </div> </div> <p class="info">Flip should work best in Google Chrome, decent in Firefox, IE10 and Opera;</p> </div>
CSS
body, html{margin:0;font:normal 0/0 Arial, Helvetica, sans-serif;background:#333; overflow:hidden;color:#fff; -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } body, html, .flipper, .f, .b, #g {width:100%;height:100%;} /* font-face */ @font-face{font-family: "GeneralFoundicons";font-weight:normal;font-style:normal; src: url("https://s3-us-west-2.amazonaws.com/s.cdpn.io/60583/general_foundicons.eot"); src: url("https://s3-us-west-2.amazonaws.com/s.cdpn.io/60583/ general_foundicons.eot?#iefix") format("embedded-opentype"), url("https://s3-us-west-2.amazonaws.com/s.cdpn.io/60583/general_foundicons.woff") format("woff"), url("https://s3-us-west-2.amazonaws.com/s.cdpn.io/60583/general_foundicons.ttf") format("truetype"), url("https://s3-us-west-2.amazonaws.com/s.cdpn.io/60583/general_foundicons .svg#GeneralFoundicons") format("svg"); } a{text-decoration:none;color:#fff;display:block;font:bold 18px/75px 'Open Sans';text-transform:uppercase;text-align:center;padding:0;margin:0 1px;} a:hover{color:#fff;background:rgba(255, 255, 255, .1)} /* FRONT SCREEN */ .logo{width:450px;height:450px;position:absolute;top:50%;left:50%;margin:-225px 0 0 -225px} .logo .card{width:50%;height:50%;cursor:default;position:relative} .logo .card.left.active{z-index:2} .logo .card.left.active + .card{opacity:0} .logo .card.left.active .b{margin-left:-100%} .logo .f{font:normal 130px/225px 'Open Sans';text-align:center;text-transform:uppercase;} .logo .c2{transform:scale(-1, 1);-webkit-transform:scale(-1, 1); -moz-transform:scale(-1, 1);-ms-transform:scale(-1, 1)} .logo .contentbox{width:200%;font:normal 12px/16px 'Open Sans';text-align:left} .logo .contentbox.levels{width:100%} .logo .contentbox .padded{padding:0 15px;} .logo .contentbox h2{font-size:24px;padding:0;margin:28px 0;text-transform:uppercase} .logo .contentbox p{line-height:14px;margin:14px 0} #stats h2 i{margin-right:10px;vertical-align:top;font:italic normal 9px/8px 'Open Sans'; color:rgba(255,255,255, .4)} #stats ul{width:50%;display:inline-block;margin:0;padding:0;list-style-type:none; vertical-align:top;line-height:22px} #stats li{display:block;margin:0;padding:0;} #stats li b{display:inline-block;font-size:12px;width:95px} #stats a {position:absolute;bottom:0;width:100%;margin-left:-15px;} .logo .info{font-size:11px;line-height:16px;text-align:center;color: rgba(255,255,255, .3);padding:0 25px;} /*GAME SCREEN*/ #g{margin-top:4px} #g .timer{width:0%;height:5px;position:absolute;top:0;background:#C0392B;z-index:1} @-webkit-keyframes timer {100% { width:100%; }} @-moz-keyframes timer {100% { width:100%; }} @-o-keyframes timer {100% { width:100%; }} @keyframes timer {100% { width:100%; }} .pause{position:absolute;width:100%;height:100%;left:0;top:0;z-index:100; background:rgba(51, 51, 51, 0.78)} .pause::before{content:'PAUSED';width:100%;position:absolute; margin-top:-100px;top:50%;background:#C0392B;color:#fff;font:bold 80px/200px 'Open Sans'; display:block;text-align:center;} /* card styles */ #g .found{display:inline-block} .card{display:inline-block;-webkit-perspective:1000;-moz-perspective:1000;} .card:not(.active):hover{opacity:.8} .card .b::before{content:attr(data-f);display:block;font:normal 100px/100px "GeneralFoundicons"; position:absolute;top:50%;margin-top:-50px;width:100%;text-align:center} /* flip cards */ .flipper {position:relative;-webkit-transform-style:preserve-3d; -moz-transform-style:preserve-3d} .f, .b {position:absolute;top:0;left:0;box-shadow:inset 0 0 0 1px #333; -webkit-backface-visibility:hidden;-moz-backface-visibility:hidden} .f {background:#16A085;z-index:1} .b {background:#C0392B;opacity:0} .card.active .b{z-index:2;opacity:1} /*Grouped for easy prefixing*/ .card, .flipper, .logo .b{transition:400ms;-ms-transition:400ms; -webkit-transition:400ms;-moz-transition:400ms;-o-transition:400ms} .card.active .flipper, .b {transform:rotateY(180deg); -webkit-transform:rotateY(180deg);-moz-transform:rotateY(180deg); -ms-transform:rotateY(180deg);-o-transform:rotateY(180deg)} .logo .f, .card .b::before, .pause::before{-webkit-text-stroke: 1px #fff} /*webkit only*/ /*Responsive Stuff*/ @media all and (max-width: 1680px) and (min-width: 640px){ .card .b::before{font-size:90px} #g.medium .card .b::before{font-size:70px} #g.hard .card .b::before{font-size:50px} } @media all and (max-width: 640px) and (min-width: 470px){ .card .b::before{font-size:90px} #g.medium .card .b::before{font-size:60px} #g.hard .card .b::before{font-size:50px} } @media all and (max-width: 470px) and (min-width: 0px){ .card .b::before{font-size:60px} #g.medium .card .b::before{font-size:35px;-webkit-text-stroke:0;} #g.hard .card .b::before{font-size:32px;-webkit-text-stroke:0;} } @media all and (max-width: 640px) and (min-width: 0px){ a{line-height:50px;font-size:13px} .logo{width:300px;height:300px;margin:-150px 0 0 -150px} .logo .f{font:normal 80px/150px 'Open Sans'} .logo .contentbox h2{font-size:16px;margin:24px 0;} .logo .instructions h2{display:none} .logo .instructions.contentbox p:nth-child(3){display:none} #stats li b{width:auto} }
JS
$(function(){ function set(key, value) { localStorage.setItem(key, value); } function get(key) { return localStorage.getItem(key); } function increase(el) { set(el, parseInt( get(el) ) + 1); } function decrease(el) { set(el, parseInt( get(el) ) - 1); } var toTime = function(nr){ if(nr == '-:-') return nr; else { var n = ' '+nr/1000+' '; return n.substr(0, n.length-1)+'s'; } }; function updateStats(){ $('#stats').html('<div class="padded"><h2>Figures: <span>'+ '<b>'+get('flip_won')+'</b><i>Won</i>'+ '<b>'+get('flip_lost')+'</b><i>Lost</i>'+ '<b>'+get('flip_abandoned')+'</b><i>Abandoned</i></span></h2>'+ '<ul><li><b>Best Casual:</b> <span>'+toTime( get('flip_casual') )+'</span></li>'+ '<li><b>Best Medium:</b> <span>'+toTime( get('flip_medium') )+'</span></li>'+ '<li><b>Best Hard:</b> <span>'+toTime( get('flip_hard') )+'</span></li></ul>'+ '<ul><li><b>Total Flips:</b> <span>'+parseInt( ( parseInt(get('flip_matched')) + parseInt(get('flip_wrong')) ) * 2)+'</span></li>'+ '<li><b>Matched Flips:</b> <span>'+get('flip_matched')+'</span></li>'+ '<li><b>Wrong Flips:</b> <span>'+get('flip_wrong')+'</span></li></ul></div>'); }; function shuffle(array) { var currentIndex = array.length, temporaryValue, randomIndex; while (0 !== currentIndex) { randomIndex = Math.floor(Math.random() * currentIndex); currentIndex -= 1; temporaryValue = array[currentIndex]; array[currentIndex] = array[randomIndex]; array[randomIndex] = temporaryValue; } return array; }; function startScreen(text){ $('#g').removeAttr('class').empty(); $('.logo').fadeIn(250); $('.c1').text(text.substring(0, 1)); $('.c2').text(text.substring(1, 2)); $('.c3').text(text.substring(2, 3)); $('.c4').text(text.substring(3, 4)); // If won game if(text == 'nice'){ increase('flip_won'); decrease('flip_abandoned'); } // If lost game else if(text == 'fail'){ increase('flip_lost'); decrease('flip_abandoned'); } // Update stats updateStats(); }; /* LOAD GAME ACTIONS */ // Init localStorage if( !get('flip_won') && !get('flip_lost') && !get('flip_abandoned') ){ //Overall Game stats set('flip_won', 0); set('flip_lost', 0); set('flip_abandoned', 0); //Best times set('flip_casual', '-:-'); set('flip_medium', '-:-'); set('flip_hard', '-:-'); //Cards stats set('flip_matched', 0); set('flip_wrong', 0); } // Fill stats if( get('flip_won') > 0 || get('flip_lost') > 0 || get('flip_abandoned') > 0) {updateStats();} // Toggle start screen cards $('.logo .card:not(".twist")').on('click', function(e){ $(this).toggleClass('active').siblings().not('.twist').removeClass('active'); if( $(e.target).is('.playnow') ) { $('.logo .card').last().addClass('active'); } }); // Start game $('.play').on('click', function(){ increase('flip_abandoned'); $('.info').fadeOut(); var difficulty = '', timer = 1000, level = $(this).data('level'); // Set game timer and difficulty if (level == 8) { difficulty = 'casual'; timer *= level * 4; } else if(level == 18) { difficulty = 'medium'; timer *= level * 5; } else if(level == 32) { difficulty = 'hard'; timer *= level * 6; } $('#g').addClass(difficulty); $('.logo').fadeOut(250, function(){ var startGame = $.now(), obj = []; // Create and add shuffled cards to game for(i = 0; i < level; i++) { obj.push(i); } var shu = shuffle( $.merge(obj, obj) ), cardSize = 100/Math.sqrt(shu.length); for(i = 0; i < shu.length; i++){ var code = shu[i]; if(code < 10) code = "0" + code; if(code == 30) code = 10; if(code == 31) code = 21; $('<div class="card" style="width:'+cardSize+'%;height:'+cardSize+'%;">'+ '<div class="flipper"><div class="f"></div><div class="b" data-f="ð'+code+';"></div></div>'+ '</div>').appendTo('#g'); } // Set card actions $('#g .card').on({ 'mousedown' : function(){ if($('#g').attr('data-paused') == 1) {return;} var data = $(this).addClass('active').find('.b').attr('data-f'); if( $('#g').find('.card.active').length > 1){ setTimeout(function(){ var thisCard = $('#g .active .b[data-f='+data+']'); if( thisCard.length > 1 ) { thisCard.parents('.card').toggleClass('active card found').empty(); //yey increase('flip_matched'); // Win game if( !$('#g .card').length ){ var time = $.now() - startGame; if( get('flip_'+difficulty) == '-:-' || get('flip_'+difficulty) > time ){ set('flip_'+difficulty, time); // increase best score } startScreen('nice'); } } else { $('#g .card.active').removeClass('active'); // fail increase('flip_wrong'); } }, 401); } } }); // Add timer bar $('<i class="timer"></i>') .prependTo('#g') .css({ 'animation' : 'timer '+timer+'ms linear' }) .one('webkitAnimationEnd oanimationend msAnimationEnd animationend', function(e) { startScreen('fail'); // fail game }); // Set keyboard (p)ause and [esc] actions $(window).off().on('keyup', function(e){ // Pause game. (p) if(e.keyCode == 80){ if( $('#g').attr('data-paused') == 1 ) { //was paused, now resume $('#g').attr('data-paused', '0'); $('.timer').css('animation-play-state', 'running'); $('.pause').remove(); } else { $('#g').attr('data-paused', '1'); $('.timer').css('animation-play-state', 'paused'); $('<div class="pause"></div>').appendTo('body'); } } // Abandon game. (ESC) if(e.keyCode == 27){ startScreen('flip'); // If game was paused if( $('#g').attr('data-paused') == 1 ){ $('#g').attr('data-paused', '0'); $('.pause').remove(); } $(window).off(); } }); }); }); });
7. Markdown-генератор
Уровень: intermediate
Описание: данное приложение преобразует обычную таблицу с данными в форматированную Markdown-таблицу.
Возможности:
- пользователь может создать HTML- таблицу с заданным количеством строк и столбцов;
- юзер может вставлять текст в каждую ячейку таблицы;
- можно генерировать Markdown-таблицу, содержащую данные из HTML-таблицы;
- можно просматривать форматированную таблицу Markdown-таблицу.
Бонус:
- реализовать возможность копирования форматированной таблицы в буфер обмена;
- реализовать возможность вставить новую строку или столбец в определенное место;
- добавить возможность полностью удалить строку или столбец.
Полезные ссылки:
Пример:
8. Линии
Данный кандидат из списка веб-проектов уже реализован много раз, но его необходимо выполнить для тренировки своих навыков.
Уровень: intermediate
Описание: приложение автоматически рисует разноцветные линии. Когда линия касается края окна, она меняет свое направление. Полосы постепенно исчезают. Библиотеки анимации не используются. Применяйте только Vanilla HTML/CSS/язык JavaScript.
Возможности:
- линии появляются в рандомном положении в пределах границы ее окружающего окна;
- каждые 20 мс отображается новая копия линии в новом положении на основе траектории предыдущей линии;
- когда конечная точка линии касается границы окна, ее направление и угол меняется случайным образом;
- после отрисовки 10-20 линий "старые" линии постепенно исчезают.
Бонус:
- пользователь может указывать длину линии и скорость ее движения;
- юзер имеет возможность установить появление нескольких линий и указать их траекторию и скорость.
Полезные ссылки:
Пример:
HTML
<div class="wrapper"> <h1>Codevember 01</h1> <canvas id="canvas"></canvas> </div>
CSS
$yellow: #fff275; $pink: #f564a9; $blue: #7ee8fa; $purple: #3a1772; $white: #fff; * { margin: 0; padding: 0; position: relative; } .wrapper { background-image: linear-gradient(to right bottom, $yellow, $pink); height: 100vh; padding: 20px 40px; width: 100vw; } h1 { border-bottom: 4px solid $blue; color: $blue; display: inline-block; font-family: 'Monoton'; font-weight: 400; font-size: 70px; text-shadow: -4px 4px 0 $purple, -8px 8px 0 $white; z-index: 9; &::before { background-color: $purple; content: ''; display: block; height: 4px; position: absolute; top: 100%; transform: translateX(-4px) translateY(6px); width: 100%; } &::after { background-color: $white; content: ''; display: block; height: 4px; position: absolute; top: 100%; transform: translateX(-8px) translateY(12px); width: 100%; } } #canvas { height: 100%; left: 0; position: absolute; top: 0; width: 100%; }
JS
const canvas = document.getElementById('canvas'); const c = canvas.getContext('2d'); let lineCoords; let lineDirs = { x1: 'neg', y1: 'neg', x2: 'pos', y2: 'pos' } let strokeHue = 180; let strokeLightness = 50; let timeoutId; let counter = 0; setup(); window.addEventListener('resize', setup); function setup() { clearTimeout(timeoutId); canvas.height = canvas.clientHeight; canvas.width = canvas.clientWidth; lineCoords = { x1: Math.floor(Math.random() * canvas.width), y1: Math.floor(Math.random() * canvas.height), x2: Math.floor(Math.random() * canvas.width), y2: Math.floor(Math.random() * canvas.height) } c.lineWidth = 3; c.lineCap = 'round'; draw(); } function draw() { if (counter > 500) { c.clearRect(0, 0, canvas.width, canvas.height); counter = 0; } counter++; c.beginPath(); c.moveTo(lineCoords.x1, lineCoords.y1); c.lineTo(lineCoords.x2, lineCoords.y2); c.strokeStyle = `hsl(${strokeHue}, ${strokeLightness}%, 85%)`; strokeHue = strokeHue + 5; if (strokeHue > 360) { strokeHue = 0; } c.stroke(); const x1dir = lineDirs.x1; const y1dir = lineDirs.y1; const x2dir = lineDirs.x2; const y2dir = lineDirs.y2; ['x1', 'y1', 'x2', 'y2'].forEach((point) => { if (lineDirs[point] === 'neg' && lineCoords[point] < 0) { lineDirs[point] = 'pos'; } else if (point[0] === 'y' && lineCoords[point] > canvas.height) { lineDirs[point] = 'neg'; } else if (point[0] === 'x' && lineCoords[point] > canvas.width) { lineDirs[point] = 'neg'; } if (lineDirs[point] === 'neg') { lineCoords[point] = lineCoords[point] - 25; } else { lineCoords[point] = lineCoords[point] + 25; } }); timeoutId = setTimeout(() => draw(), 200); }
9. Список дел
Уровень: intermediate
Описание: классическое приложение, где пользователь может записать все мысли, задачи и цели, чтобы не забыть.
Возможности:
- ввод задачи в специальное поле;
- сохранение и появление в списке новой задачи по нажатию на кнопку;
- возможность выделять выполненный пункт;
- удаление из списка любого пункта.
Бонус:
- возможность редактировать любой пункт;
- просмотр выполненных и активных задач, даты их создания;
- при закрытии окна/браузера данные должны сохраняться, а при "возвращении" пользователя динамически подгружены.
Полезные ссылки:
Пример:
HTML
<div class="tdl-holder"> <h2>TO DO LIST</h2> <div class="tdl-content"> <ul> <li><label><input type="checkbox"><i></i><span>get up</span><a href='#'>–</a></label></li> <li><label><input type="checkbox" checked><i></i><span>stand up</span><a href='#'>–</a> </label></li> <li><label><input type="checkbox"><i></i><span>don't give up the fight.</span> <a href='#'>–</a></label></li> <li><label><input type="checkbox" checked><i></i><span>save the world.</span> <a href='#'>–</a></label></li> <li><label><input type="checkbox"><i></i><span>do something else</span> <a href='#'>–</a></label></li> </ul> </div> <input type="text" class="tdl-new" placeholder="Write new item and hit 'Enter'..."> </div>
CSS
@import url(https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900); @import url(https://fonts.googleapis.com/css?family=Roboto+Condensed:300,400,700); body{ color:#4a5b65; font-family: "Roboto", Arial, sans-serif; font-size:14px; line-height: 20px; } *:focus{outline:none !important;} /* TO DO LIST ================================================== */ .tdl-holder{ margin:0px auto; width: 300px; } .tdl-holder h2{ background-color: #de3f53; border-top-left-radius: 4px; border-top-right-radius: 4px; color:#fff; font-family:"Roboto Condensed", Arial, sans-serif; font-size:16px; font-weight: 100; line-height: 56px; padding-left: 15px; margin:0; } .tdl-holder ul, .tdl-holder li { list-style: none; margin:0; padding:0; } .tdl-holder li{ background-color: #262e4c; border-bottom:1px solid #1c2340; color: #b1b2c9; } .tdl-holder li span{ margin-left:30px; -webkit-transition: all .2s linear; -moz-transition: all .2s linear; -o-transition: all .2s linear; transition: all .2s linear; } .tdl-holder label{ cursor:pointer; display:block; line-height: 56px; padding: 0 15px; position: relative; } .tdl-holder label:hover{ background-color: #2a3353; color:#8284a3; } .tdl-holder label a{ background-color:#de3f53; border-radius:50%; color:#fff; display:none; float:right; font-weight: bold; line-height: normal; height:16px; margin-top: 20px; text-align: center; text-decoration: none; width:16px; -webkit-transition: all .2s linear; -moz-transition: all .2s linear; -o-transition: all .2s linear; transition: all .2s linear; } .tdl-holder label:hover a{ display: block; } .tdl-holder label a:hover{ background-color:#fff; color:#de3f53; } .tdl-holder input[type="checkbox"]{ cursor: pointer; opacity: 0; position: absolute; } .tdl-holder input[type="checkbox"] + i{ background-color: #404a6e; border-radius: 50%; display: block; height: 16px; position: absolute; top:20px; width: 16px; z-index: 1; } .tdl-holder input[type="checkbox"]:checked + i::after{ background-color: #6E6E96; border-radius: 50%; content: ''; display: block; height:8px; left:4px; position: absolute; top:4px; width:8px; z-index: 2; } .tdl-holder input[type="checkbox"]:checked ~ span{ color: #586186; text-decoration: line-through; } .tdl-holder input[type="text"]{ background-color: #171d37; border: none; border-bottom-left-radius: 4px; border-bottom-right-radius: 4px; box-shadow: inset 0 0 8px 0 #0e1329; color: #464f72; font-size:14px; margin:0; padding:20px 15px; width:270px; -webkit-transition: all .2s linear; -moz-transition: all .2s linear; -o-transition: all .2s linear; transition: all .2s linear; } .tdl-holder input[type="text"]:hover{ color:#4c577f; } .tdl-holder input[type="text"]:focus{ color:#fff; } .tdl-holder ::-webkit-input-placeholder {color: #464f72;} /* WebKit browsers */ .tdl-holder :-moz-placeholder {color: #464f72;} /* Mozilla Firefox 4 to 18 */ .tdl-holder ::-moz-placeholder {color: #464f72;} /* Mozilla Firefox 19+ */ .tdl-holder :-ms-input-placeholder {color: #464f72;} /* Internet Explorer 10+ */ .tdl-holder li.remove{ -webkit-animation:collapseItem 300ms ease; animation:collapseItem 300ms ease; -webkit-transform-origin: 50% 0%; -ms-transform-origin: 50% 0%; transform-origin: 50% 0%; } .tdl-holder li.remove span{ color: #586186; text-decoration: line-through; } @keyframes collapseItem { 0% { -ms-transform: perspective(500px) rotateX(0deg);transform: perspective (500px) rotateX(0deg); } 100% { -ms-transform: perspective(500px) rotateX(-90deg);transform: perspective (500px) rotateX(-90deg); } } @-webkit-keyframes collapseItem { 0% { -webkit-transform: perspective(500px) rotateX(0deg); } 100% { -webkit-transform: perspective(500px) rotateX(-90deg);} }
JS
/* TO DO LIST */ $(".tdl-new").bind('keypress', function(e){ var code = (e.keyCode ? e.keyCode : e.which); if(code == 13) { var v = $(this).val(); var s = v.replace(/ +?/g, ''); if (s == ""){ return false; }else{ $(".tdl-content ul").append("<li><label><input type='checkbox'><i></i><span>"+ v +"</span><a href='#'>–</a></label></li>"); $(this).val(""); } } }); $(".tdl-content a").bind("click", function(){ var _li = $(this).parent().parent("li"); _li.addClass("remove").stop().delay(100).slideUp("fast", function(){ _li.remove(); }); return false; }); // for dynamically created a tags $(".tdl-content").on('click', "a", function(){ var _li = $(this).parent().parent("li"); _li.addClass("remove").stop().delay(100).slideUp("fast", function(){ _li.remove(); }); return false; });
10. Морской бой
Уровень: advanced
Описание: текстовый вариант графической версии игры. Игра состоит из движка (BGE) и консольной части.
Возможности:
BGE
- для запуска используется функция startGame(), чтобы начать игру с 1 игроком: функция создаст игровую доску 8x8, состоящую из 3 кораблей, размещенных рандомно;
- для выстрела используется функция shoot(), передающая координаты пересечения строки и столбца на игровом поле. Функция вернет информацию о статусе (попадание или промах), количестве оставшихся кораблей, а также массивы размещения кораблей, попаданий и промахов.
Консольная часть
- пользователь может видеть массив выстрелов, отображаемый в двумерном виде, возвращаемый функцией startGame();
- юзеру предлагается ввести координаты целевой ячейки;
- все попадания и промахи, а также сообщения о результате выстрела и поздравление отображаются после выстрела;
- в конце игры предлагается повторить раунд.
Полезные ссылки:
Пример:
11. Чат
Это приложение самое интересное из подборки веб-проектов и самое популярное на данные момент.
Уровень: advanced
Описание: классический мессенджер.
Возможности:
- пользователю предлагается ввести свой ник при входе (он будет сохранен в приложении);
- присутствует поле для ввода сообщения и кнопка отправки. Отправленное сообщение появится в окне чата рядом с ником.
Бонус:
- сообщения должны быть видны всем пользователям чата (WebSockets);
- когда новый пользователь присоединяется к чату, всем юзерам приходит сообщение;
- сообщения хранятся в БД;
- в чат можно посылать смайлы, изображения, видео и ссылки;
- есть возможность создать приватную комнату.
Полезные ссылки:
Пример:
HTML
<div class="wrapper"></div> <input class="message" type="text" placeholder="Enter message and press enter"/> <div class="initModal"> <h3>What's your name?</h3> <input type="text" class="username" placeholder="Enter your name and press enter"/> </div>
CSS
//usual reset stuff *,*:before,*:after,ul,li,a,button,input,h1,h2,h3,h4,h5,h6,p, img, image, svg, path, g { margin: 0; padding: 0; box-sizing: border-box; background-color: transparent; border: none; text-decoration: none; font-family: 'Roboto'; user-select: none; list-style: none; } $swipes-shadow-one: 0 1px 3px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.12); $swipes-shadow-two: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23); $swipes-shadow-three: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23); $swipes-shadow-four: 0 14px 28px rgba(0,0,0,0.25), 0 10px 10px rgba(0,0,0,0.22); $swipes-shadow-five: 0 19px 38px rgba(0,0,0,0.30), 0 15px 12px rgba(0,0,0,0.22); html, body { width: 100%; height: 100%; margin: 0; padding: 0; background-color: #eee; font-family: 'Roboto'; } li { opacity: 0; animation: fadeIn .4s ease-in-out forwards; } @keyframes fadeIn { 100% { opacity: 1; } } .wrapper { width: 100%; max-width: 650px; margin: 0 auto; padding-bottom: 70px; overflow-x: hidden; word-break: break-all; } li { padding: 10px 20px; color: #34495e; word-break: break-all; span { color: #e74c3c; font-weight: 700; } } input.message { width: 100%; max-width: 650px; //border: 1px solid #333; position: fixed; bottom: 0; height: 60px; background-color: #fff; box-shadow: $swipes-shadow-one; padding: 0 20px; left: 50%; transform: translateX(-50%); &:focus { outline: none; } } .initModal { width: 100%; height: 100%; position: fixed; left: 0; top: 0; background-color: #fff; display: flex; align-items: center; justify-content: center; flex-direction: column; h3 { font-weight: 300; color: #777; } input.username { width: 350px; height: 60px; background-color: #eee; padding: 0 15px; margin-top: 25px; color: #444; &:focus { outline: none; } } }
JS
//If you fork this, please change this database link to your own. var fb = new Firebase("https://codepentestt.firebaseio.com/"); var messages = fb.child("messages"); var btn = $('button'); var wrap = $('.wrapper'); var input = $('input.message'); var usernameInput = $('input.username'); var user = []; (function($) { $.sanitize = function(input) { var output = input.replace(/<script[^>]*?>.*?<\/script>/gi, ''). replace(/<[\/\!]*?[^<>]*?>/gi, ''). replace(/<style[^>]*?>.*?<\/style>/gi, ''). replace(/<![\s\S]*?--[ \t\n\r]*>/gi, ''); return output; }; })(jQuery); usernameInput.on('keyup', function(e) { if (e.keyCode === 13 && usernameInput.val().length > 0) { var getTxt = usernameInput.val(); user.push(getTxt); usernameInput.val(''); $('.initModal').css('display', 'none'); console.log(user); } }); input.on('keyup', function(e) { var curUsername = user.join(); if (e.keyCode === 13 && input.val().length > 0) { var getTxt = input.val(); messages.push({ user: curUsername, message: getTxt }); input.val(''); } }); messages.limitToLast(100).on("child_added", function(snap) { wrap.append('<li><span>' + $.sanitize(snap.val().user) + ':</span> ' + $.sanitize(snap.val().message) + '</li>'); window.scrollTo(0,document.body.scrollHeight); });
Следующий в списке веб-проектов – классная полезность для Git-а. Зацените!
12. GitHub Timeline
Уровень: advanced
Описание: приложение должно показывать всю историю создания репозиториев, их имена и даты создания по конкретному юзеру. График можно расшарить. Используются только публичные репозитории.
Возможности:
- ввод имени пользователя GitHub;
- по нажатию на кнопку генерируется график активности данного пользователя;
- если такого юзера не существует – выводится сообщение об ошибке.
Бонус:
- возможность просматривать репозитории по году создания.
Полезные ссылки:
Пример:
HTML
<link href='https://fonts.googleapis.com/css?family=Open+Sans:400,300,300italic,400italic,600, 600italic,700,700italic' rel='stylesheet' type='text/css'> <!-- The Timeline --> <ul class="timeline"> <!-- Item 1 --> <li> <div class="direction-r"> <div class="flag-wrapper"> <span class="flag">Freelancer</span> <span class="time-wrapper"><span class="time">2013 - present </span></span> </div> <div class="desc">My current employment. Way better than the position before!</div> </div> </li> <!-- Item 2 --> <li> <div class="direction-l"> <div class="flag-wrapper"> <span class="flag">Apple Inc.</span> <span class="time-wrapper"><span class="time"> 2011 - 2013</span></span> </div> <div class="desc">My first employer. All the stuff I've learned and projects I've been working on.</div> </div> </li> <!-- Item 3 --> <li> <div class="direction-r"> <div class="flag-wrapper"> <span class="flag">Harvard University</span> <span class="time-wrapper"><span class="time"> 2008 - 2011</span></span> </div> <div class="desc">A description of all the lectures and courses I have taken and my final degree?</div> </div> </li> </ul>
CSS
body { margin: 0; padding: 0; background: rgb(230,230,230); color: rgb(50,50,50); font-family: 'Open Sans', sans-serif; font-size: 112.5%; line-height: 1.6em; } /* ================ The Timeline ================ */ .timeline { position: relative; width: 660px; margin: 0 auto; margin-top: 20px; padding: 1em 0; list-style-type: none; } .timeline:before { position: absolute; left: 50%; top: 0; content: ' '; display: block; width: 6px; height: 100%; margin-left: -3px; background: rgb(80,80,80); background: -moz-linear-gradient(top, rgba(80,80,80,0) 0%, rgb(80,80,80) 8%, rgb(80,80,80) 92%, rgba(80,80,80,0) 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, rgba(30,87,153,1)), color-stop(100%,rgba(125,185,232,1))); background: -webkit-linear-gradient(top, rgba(80,80,80,0) 0%, rgb(80,80,80) 8%, rgb(80,80,80) 92%, rgba(80,80,80,0) 100%); background: -o-linear-gradient(top, rgba(80,80,80,0) 0%, rgb(80,80,80) 8%, rgb(80,80,80) 92%, rgba(80,80,80,0) 100%); background: -ms-linear-gradient(top, rgba(80,80,80,0) 0%, rgb(80,80,80) 8%, rgb(80,80,80) 92%, rgba(80,80,80,0) 100%); background: linear-gradient(to bottom, rgba(80,80,80,0) 0%, rgb(80,80,80) 8%, rgb(80,80,80) 92%, rgba(80,80,80,0) 100%); z-index: 5; } .timeline li { padding: 1em 0; } .timeline li:after { content: ""; display: block; height: 0; clear: both; visibility: hidden; } .direction-l { position: relative; width: 300px; float: left; text-align: right; } .direction-r { position: relative; width: 300px; float: right; } .flag-wrapper { position: relative; display: inline-block; text-align: center; } .flag { position: relative; display: inline; background: rgb(248,248,248); padding: 6px 10px; border-radius: 5px; font-weight: 600; text-align: left; } .direction-l .flag { -webkit-box-shadow: -1px 1px 1px rgba(0,0,0,0.15), 0 0 1px rgba(0,0,0,0.15); -moz-box-shadow: -1px 1px 1px rgba(0,0,0,0.15), 0 0 1px rgba(0,0,0,0.15); box-shadow: -1px 1px 1px rgba(0,0,0,0.15), 0 0 1px rgba(0,0,0,0.15); } .direction-r .flag { -webkit-box-shadow: 1px 1px 1px rgba(0,0,0,0.15), 0 0 1px rgba(0,0,0,0.15); -moz-box-shadow: 1px 1px 1px rgba(0,0,0,0.15), 0 0 1px rgba(0,0,0,0.15); box-shadow: 1px 1px 1px rgba(0,0,0,0.15), 0 0 1px rgba(0,0,0,0.15); } .direction-l .flag:before, .direction-r .flag:before { position: absolute; top: 50%; right: -40px; content: ' '; display: block; width: 12px; height: 12px; margin-top: -10px; background: #fff; border-radius: 10px; border: 4px solid rgb(255,80,80); z-index: 10; } .direction-r .flag:before { left: -40px; } .direction-l .flag:after { content: ""; position: absolute; left: 100%; top: 50%; height: 0; width: 0; margin-top: -8px; border: solid transparent; border-left-color: rgb(248,248,248); border-width: 8px; pointer-events: none; } .direction-r .flag:after { content: ""; position: absolute; right: 100%; top: 50%; height: 0; width: 0; margin-top: -8px; border: solid transparent; border-right-color: rgb(248,248,248); border-width: 8px; pointer-events: none; } .time-wrapper { display: inline; line-height: 1em; font-size: 0.66666em; color: rgb(250,80,80); vertical-align: middle; } .direction-l .time-wrapper { float: left; } .direction-r .time-wrapper { float: right; } .time { display: inline-block; padding: 4px 6px; background: rgb(248,248,248); } .desc { margin: 1em 0.75em 0 0; font-size: 0.77777em; font-style: italic; line-height: 1.5em; } .direction-r .desc { margin: 1em 0 0 0.75em; } /* ================ Timeline Media Queries ================ */ @media screen and (max-width: 660px) { .timeline { width: 100%; padding: 4em 0 1em 0; } .timeline li { padding: 2em 0; } .direction-l, .direction-r { float: none; width: 100%; text-align: center; } .flag-wrapper { text-align: center; } .flag { background: rgb(255,255,255); z-index: 15; } .direction-l .flag:before, .direction-r .flag:before { position: absolute; top: -30px; left: 50%; content: ' '; display: block; width: 12px; height: 12px; margin-left: -9px; background: #fff; border-radius: 10px; border: 4px solid rgb(255,80,80); z-index: 10; } .direction-l .flag:after, .direction-r .flag:after { content: ""; position: absolute; left: 50%; top: -8px; height: 0; width: 0; margin-left: -8px; border: solid transparent; border-bottom-color: rgb(255,255,255); border-width: 8px; pointer-events: none; } .time-wrapper { display: block; position: relative; margin: 4px 0 0 0; z-index: 14; } .direction-l .time-wrapper { float: none; } .direction-r .time-wrapper { float: none; } .desc { position: relative; margin: 1em 0 0 0; padding: 1em; background: rgb(245,245,245); -webkit-box-shadow: 0 0 1px rgba(0,0,0,0.20); -moz-box-shadow: 0 0 1px rgba(0,0,0,0.20); box-shadow: 0 0 1px rgba(0,0,0,0.20); z-index: 15; } .direction-l .desc, .direction-r .desc { position: relative; margin: 1em 1em 0 1em; padding: 1em; z-index: 15; } } @media screen and (min-width: 400px ?? max-width: 660px) { .direction-l .desc, .direction-r .desc { margin: 1em 4em 0 4em; } }
13. Spell-It
Уровень: advanced
Описание: приложение Spell-It помогает пользователям практиковать свое правописание, воспроизводя слова в аудиозаписи, которые пользователь затем должен написать. Применяется углубленное программирование JavaScript.
Возможности:
- по нажатию на кнопку "Play" воспроизводится слово, которое должно быть введено;
- кнопка "Enter" отправляет введенное слово на проверку;
- при правильном/неправильном вводе слова появляется сообщение об ошибке или успехе.
Бонус:
- реализовать звуковое оповещение, когда слово правильно написано;
- реализовать подачу предупреждающего сигнала при неправильном написании;
- создать кнопку, "подчеркивающую" ошибки в слове.
Полезные ссылки:
Пример:
Понравилось? Вас также заинтересуют:
- Хватит использовать массивы! Как JavaScript Set ускоряет код
- Примеры JavaScript: 7 приёмов, о которых вы не знали
- 12 JavaScript-трюков, которым не учат новичков