Иногда хочется написать что-то эдакое, а идей нет. Вот вам подборка классных идей для веб-проектов на любой случай и вкус.
Данные приложения поспособствуют:
- поднятию уровня знаний по программированию;
- изучению новых технологий;
- созданию нового проекта в портфолио;
- созданию продукта, который можно легко расширить доп. возможностями.

Список идей разделен на три базовых уровня сложности:
- 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-трюков, которым не учат новичков
Комментарии