Merge branch 'about-page'

This commit is contained in:
yafeilee 2014-10-10 10:01:59 +08:00
commit b25e10dd2c
13 changed files with 976 additions and 52 deletions

View File

@ -12,6 +12,7 @@ gem 'uglifier', '>= 1.3.0'
gem 'jquery-rails'
gem 'foundation-rails', '~> 5.2.1'
gem 'foundation-icons-sass-rails'
gem 'font-awesome-sass'
gem 'mongoid'
gem 'mongoid-pagination'

View File

@ -73,6 +73,8 @@ GEM
ffi (1.9.5)
figaro (1.0.0)
thor (~> 0.14)
font-awesome-sass (4.0.2)
sass-rails (>= 3.1.1)
formatador (0.2.5)
foundation-icons-sass-rails (3.0.0)
railties (>= 3.1.1)
@ -285,6 +287,7 @@ DEPENDENCIES
database_cleaner
factory_girl_rails
figaro
font-awesome-sass
foundation-icons-sass-rails
foundation-rails (~> 5.2.1)
guard

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="图形" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="1024px" height="1024px" viewBox="0 0 1024 1024" enable-background="new 0 0 1024 1024" xml:space="preserve">
<path fill="#219AB3" d="M926.917973 774.19392C959.65184 774.19392 986.19392 747.65184 986.19392 714.917973L986.19392-114.917973C986.19392-147.65184 959.65184-174.19392 926.917973-174.19392L97.082027-174.19392C64.34816-174.19392 37.80608-147.65184 37.80608-114.917973L37.80608 714.917973C37.80608 747.65184 64.34816 774.19392 97.082027 774.19392zM176.653653 635.80032 176.653653 559.321173 825.658027 559.321173 825.658027 635.80032zM217.719467 495.853653 217.719467 183.91936 273.524053 183.91936 341.292373 41.60384 157.259093 41.60384 157.259093-33.417813 842.949973-33.417813 842.949973 41.60384 654.226773 41.60384 722.899627 183.91936 783.67744 183.91936 783.67744 495.853653zM684.885333 419.108267 684.885333 258.012587 312.576 258.012587 312.576 419.108267zM570.770773 41.60384 426.653013 41.60384 359.621973 183.91936 639.443627 183.91936z" transform="translate(0, 812) scale(1, -1)"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

589
app/assets/javascripts/angular-scroll.js vendored Normal file
View File

@ -0,0 +1,589 @@
/**
* x is a value between 0 and 1, indicating where in the animation you are.
*/
var duScrollDefaultEasing = function (x) {
'use strict';
if(x < 0.5) {
return Math.pow(x*2, 2)/2;
}
return 1-Math.pow((1-x)*2, 2)/2;
};
angular.module('duScroll', [
'duScroll.scrollspy',
'duScroll.smoothScroll',
'duScroll.scrollContainer',
'duScroll.spyContext',
'duScroll.scrollHelpers'
])
//Default animation duration for smoothScroll directive
.value('duScrollDuration', 350)
//Scrollspy debounce interval, set to 0 to disable
.value('duScrollSpyWait', 100)
//Wether or not multiple scrollspies can be active at once
.value('duScrollGreedy', false)
//Default offset for smoothScroll directive
.value('duScrollOffset', 0)
//Default easing function for scroll animation
.value('duScrollEasing', duScrollDefaultEasing);
angular.module('duScroll.scrollHelpers', ['duScroll.requestAnimation'])
.run(["$window", "$q", "cancelAnimation", "requestAnimation", "duScrollEasing", "duScrollDuration", "duScrollOffset", function($window, $q, cancelAnimation, requestAnimation, duScrollEasing, duScrollDuration, duScrollOffset) {
'use strict';
var proto = angular.element.prototype;
var isDocument = function(el) {
return (typeof HTMLDocument !== 'undefined' && el instanceof HTMLDocument) || (el.nodeType && el.nodeType === el.DOCUMENT_NODE);
};
var isElement = function(el) {
return (typeof HTMLElement !== 'undefined' && el instanceof HTMLElement) || (el.nodeType && el.nodeType === el.ELEMENT_NODE);
};
var unwrap = function(el) {
return isElement(el) || isDocument(el) ? el : el[0];
};
proto.scrollTo = function(left, top, duration, easing) {
var aliasFn;
if(angular.isElement(left)) {
aliasFn = this.scrollToElement;
} else if(duration) {
aliasFn = this.scrollToAnimated;
}
if(aliasFn) {
return aliasFn.apply(this, arguments);
}
var el = unwrap(this);
if(isDocument(el)) {
return $window.scrollTo(left, top);
}
el.scrollLeft = left;
el.scrollTop = top;
};
var scrollAnimation, deferred;
proto.scrollToAnimated = function(left, top, duration, easing) {
if(duration && !easing) {
easing = duScrollEasing;
}
var startLeft = this.scrollLeft(),
startTop = this.scrollTop(),
deltaLeft = Math.round(left - startLeft),
deltaTop = Math.round(top - startTop);
var startTime = null;
var el = this;
var cancelOnEvents = 'scroll mousedown mousewheel touchmove keydown';
var cancelScrollAnimation = function($event) {
if (!$event || $event.which > 0) {
el.unbind(cancelOnEvents, cancelScrollAnimation);
cancelAnimation(scrollAnimation);
deferred.reject();
scrollAnimation = null;
}
};
if(scrollAnimation) {
cancelScrollAnimation();
}
deferred = $q.defer();
if(!deltaLeft && !deltaTop) {
deferred.resolve();
return deferred.promise;
}
var animationStep = function(timestamp) {
if (startTime === null) {
startTime = timestamp;
}
var progress = timestamp - startTime;
var percent = (progress >= duration ? 1 : easing(progress/duration));
el.scrollTo(
startLeft + Math.ceil(deltaLeft * percent),
startTop + Math.ceil(deltaTop * percent)
);
if(percent < 1) {
scrollAnimation = requestAnimation(animationStep);
} else {
el.unbind(cancelOnEvents, cancelScrollAnimation);
scrollAnimation = null;
deferred.resolve();
}
};
//Fix random mobile safari bug when scrolling to top by hitting status bar
el.scrollTo(startLeft, startTop);
el.bind(cancelOnEvents, cancelScrollAnimation);
scrollAnimation = requestAnimation(animationStep);
return deferred.promise;
};
proto.scrollToElement = function(target, offset, duration, easing) {
var el = unwrap(this);
if(!angular.isNumber(offset) || isNaN(offset)) {
offset = duScrollOffset;
}
var top = this.scrollTop() + unwrap(target).getBoundingClientRect().top - offset;
if(isElement(el)) {
top -= el.getBoundingClientRect().top;
}
return this.scrollTo(0, top, duration, easing);
};
var overloaders = {
scrollLeft: function(value, duration, easing) {
if(angular.isNumber(value)) {
return this.scrollTo(value, this.scrollTop(), duration, easing);
}
var el = unwrap(this);
if(isDocument(el)) {
return $window.scrollX || document.documentElement.scrollLeft || document.body.scrollLeft;
}
return el.scrollLeft;
},
scrollTop: function(value, duration, easing) {
if(angular.isNumber(value)) {
return this.scrollTo(this.scrollTop(), value, duration, easing);
}
var el = unwrap(this);
if(isDocument(el)) {
return $window.scrollY || document.documentElement.scrollTop || document.body.scrollTop;
}
return el.scrollTop;
}
};
proto.scrollToElementAnimated = function(target, offset, duration, easing) {
return this.scrollToElement(target, offset, duration || duScrollDuration, easing);
};
proto.scrollTopAnimated = function(top, duration, easing) {
return this.scrollTop(top, duration || duScrollDuration, easing);
};
proto.scrollLeftAnimated = function(left, duration, easing) {
return this.scrollLeft(left, duration || duScrollDuration, easing);
};
//Add duration and easing functionality to existing jQuery getter/setters
var overloadScrollPos = function(superFn, overloadFn) {
return function(value, duration, easing) {
if(duration) {
return overloadFn.apply(this, arguments);
}
return superFn.apply(this, arguments);
};
};
for(var methodName in overloaders) {
proto[methodName] = (proto[methodName] ? overloadScrollPos(proto[methodName], overloaders[methodName]) : overloaders[methodName]);
}
}]);
//Adapted from https://gist.github.com/paulirish/1579671
angular.module('duScroll.polyfill', [])
.factory('polyfill', ["$window", function($window) {
'use strict';
var vendors = ['webkit', 'moz', 'o', 'ms'];
return function(fnName, fallback) {
if($window[fnName]) {
return $window[fnName];
}
var suffix = fnName.substr(0, 1).toUpperCase() + fnName.substr(1);
for(var key, i = 0; i < vendors.length; i++) {
key = vendors[i]+suffix;
if($window[key]) {
return $window[key];
}
}
return fallback;
};
}]);
angular.module('duScroll.requestAnimation', ['duScroll.polyfill'])
.factory('requestAnimation', ["polyfill", "$timeout", function(polyfill, $timeout) {
'use strict';
var lastTime = 0;
var fallback = function(callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = $timeout(function() { callback(currTime + timeToCall); },
timeToCall);
lastTime = currTime + timeToCall;
return id;
};
return polyfill('requestAnimationFrame', fallback);
}])
.factory('cancelAnimation', ["polyfill", "$timeout", function(polyfill, $timeout) {
'use strict';
var fallback = function(promise) {
$timeout.cancel(promise);
};
return polyfill('cancelAnimationFrame', fallback);
}]);
angular.module('duScroll.spyAPI', ['duScroll.scrollContainerAPI'])
.factory('spyAPI', ["$rootScope", "$timeout", "scrollContainerAPI", "duScrollGreedy", "duScrollSpyWait", function($rootScope, $timeout, scrollContainerAPI, duScrollGreedy, duScrollSpyWait) {
'use strict';
var createScrollHandler = function(context) {
var timer = false, queued = false;
var handler = function() {
queued = false;
var container = context.container,
containerEl = container[0],
containerOffset = 0;
if (typeof HTMLElement !== 'undefined' && containerEl instanceof HTMLElement || containerEl.nodeType && containerEl.nodeType === containerEl.ELEMENT_NODE) {
containerOffset = containerEl.getBoundingClientRect().top;
}
var i, currentlyActive, toBeActive, spies, spy, pos;
spies = context.spies;
currentlyActive = context.currentlyActive;
toBeActive = undefined;
for(i = 0; i < spies.length; i++) {
spy = spies[i];
pos = spy.getTargetPosition();
if (!pos) continue;
if(pos.top + spy.offset - containerOffset < 20 && (pos.top*-1 + containerOffset) < pos.height) {
if(!toBeActive || toBeActive.top < pos.top) {
toBeActive = {
top: pos.top,
spy: spy
};
}
}
}
if(toBeActive) {
toBeActive = toBeActive.spy;
}
if(currentlyActive === toBeActive || (duScrollGreedy && !toBeActive)) return;
if(currentlyActive) {
currentlyActive.$element.removeClass('active');
$rootScope.$broadcast('duScrollspy:becameInactive', currentlyActive.$element);
}
if(toBeActive) {
toBeActive.$element.addClass('active');
$rootScope.$broadcast('duScrollspy:becameActive', toBeActive.$element);
}
context.currentlyActive = toBeActive;
};
if(!duScrollSpyWait) {
return handler;
}
//Debounce for potential performance savings
return function() {
if(!timer) {
handler();
timer = $timeout(function() {
timer = false;
if(queued) {
handler();
}
}, duScrollSpyWait, false);
} else {
queued = true;
}
};
};
var contexts = {};
var createContext = function($scope) {
var id = $scope.$id;
var context = {
spies: []
};
context.handler = createScrollHandler(context);
contexts[id] = context;
$scope.$on('$destroy', function() {
destroyContext($scope);
});
return id;
};
var destroyContext = function($scope) {
var id = $scope.$id;
var context = contexts[id], container = context.container;
if(container) {
container.off('scroll', context.handler);
}
delete contexts[id];
};
var defaultContextId = createContext($rootScope);
var getContextForScope = function(scope) {
if(contexts[scope.$id]) {
return contexts[scope.$id];
}
if(scope.$parent) {
return getContextForScope(scope.$parent);
}
return contexts[defaultContextId];
};
var getContextForSpy = function(spy) {
var context, contextId, scope = spy.$element.scope();
if(scope) {
return getContextForScope(scope);
}
//No scope, most likely destroyed
for(contextId in contexts) {
context = contexts[contextId];
if(context.spies.indexOf(spy) !== -1) {
return context;
}
}
};
var isElementInDocument = function(element) {
while (element.parentNode) {
element = element.parentNode;
if (element === document) {
return true;
}
}
return false;
};
var addSpy = function(spy) {
var context = getContextForSpy(spy);
if (!context) return;
context.spies.push(spy);
if (!context.container || !isElementInDocument(context.container)) {
if(context.container) {
context.container.off('scroll', context.handler);
}
context.container = scrollContainerAPI.getContainer(spy.$element.scope());
context.container.on('scroll', context.handler).triggerHandler('scroll');
}
};
var removeSpy = function(spy) {
var context = getContextForSpy(spy);
if(spy === context.currentlyActive) {
context.currentlyActive = null;
}
var i = context.spies.indexOf(spy);
if(i !== -1) {
context.spies.splice(i, 1);
}
};
return {
addSpy: addSpy,
removeSpy: removeSpy,
createContext: createContext,
destroyContext: destroyContext,
getContextForScope: getContextForScope
};
}]);
angular.module('duScroll.scrollContainerAPI', [])
.factory('scrollContainerAPI', ["$document", function($document) {
'use strict';
var containers = {};
var setContainer = function(scope, element) {
var id = scope.$id;
containers[id] = element;
return id;
};
var getContainerId = function(scope) {
if(containers[scope.$id]) {
return scope.$id;
}
if(scope.$parent) {
return getContainerId(scope.$parent);
}
return;
};
var getContainer = function(scope) {
var id = getContainerId(scope);
return id ? containers[id] : $document;
};
var removeContainer = function(scope) {
var id = getContainerId(scope);
if(id) {
delete containers[id];
}
};
return {
getContainerId: getContainerId,
getContainer: getContainer,
setContainer: setContainer,
removeContainer: removeContainer
};
}]);
angular.module('duScroll.smoothScroll', ['duScroll.scrollHelpers', 'duScroll.scrollContainerAPI'])
.directive('duSmoothScroll', ["duScrollDuration", "duScrollOffset", "scrollContainerAPI", function(duScrollDuration, duScrollOffset, scrollContainerAPI) {
'use strict';
return {
link : function($scope, $element, $attr) {
$element.on('click', function(e) {
if(!$attr.href || $attr.href.indexOf('#') === -1) return;
var target = document.getElementById($attr.href.replace(/.*(?=#[^\s]+$)/, '').substring(1));
if(!target || !target.getBoundingClientRect) return;
if (e.stopPropagation) e.stopPropagation();
if (e.preventDefault) e.preventDefault();
var offset = $attr.offset ? parseInt($attr.offset, 10) : duScrollOffset;
var duration = $attr.duration ? parseInt($attr.duration, 10) : duScrollDuration;
var container = scrollContainerAPI.getContainer($scope);
container.scrollToElement(
angular.element(target),
isNaN(offset) ? 0 : offset,
isNaN(duration) ? 0 : duration
);
});
}
};
}]);
angular.module('duScroll.spyContext', ['duScroll.spyAPI'])
.directive('duSpyContext', ["spyAPI", function(spyAPI) {
'use strict';
return {
restrict: 'A',
scope: true,
compile: function compile(tElement, tAttrs, transclude) {
return {
pre: function preLink($scope, iElement, iAttrs, controller) {
spyAPI.createContext($scope);
}
};
}
};
}]);
angular.module('duScroll.scrollContainer', ['duScroll.scrollContainerAPI'])
.directive('duScrollContainer', ["scrollContainerAPI", function(scrollContainerAPI){
'use strict';
return {
restrict: 'A',
scope: true,
compile: function compile(tElement, tAttrs, transclude) {
return {
pre: function preLink($scope, iElement, iAttrs, controller) {
iAttrs.$observe('duScrollContainer', function(element) {
if(angular.isString(element)) {
element = document.getElementById(element);
}
element = (angular.isElement(element) ? angular.element(element) : iElement);
scrollContainerAPI.setContainer($scope, element);
$scope.$on('$destroy', function() {
scrollContainerAPI.removeContainer($scope);
});
});
}
};
}
};
}]);
angular.module('duScroll.scrollspy', ['duScroll.spyAPI'])
.directive('duScrollspy', ["spyAPI", "duScrollOffset", "$timeout", "$rootScope", function(spyAPI, duScrollOffset, $timeout, $rootScope) {
'use strict';
var Spy = function(targetElementOrId, $element, offset) {
if(angular.isElement(targetElementOrId)) {
this.target = targetElementOrId;
} else if(angular.isString(targetElementOrId)) {
this.targetId = targetElementOrId;
}
this.$element = $element;
this.offset = offset;
};
Spy.prototype.getTargetElement = function() {
if (!this.target && this.targetId) {
this.target = document.getElementById(this.targetId);
}
return this.target;
};
Spy.prototype.getTargetPosition = function() {
var target = this.getTargetElement();
if(target) {
return target.getBoundingClientRect();
}
};
Spy.prototype.flushTargetCache = function() {
if(this.targetId) {
this.target = undefined;
}
};
return {
link: function ($scope, $element, $attr) {
var href = $attr.ngHref || $attr.href;
var targetId;
if (href && href.indexOf('#') !== -1) {
targetId = href.replace(/.*(?=#[^\s]+$)/, '').substring(1);
} else if($attr.duScrollspy) {
targetId = $attr.duScrollspy;
}
if(!targetId) return;
// Run this in the next execution loop so that the scroll context has a chance
// to initialize
$timeout(function() {
var spy = new Spy(targetId, $element, -($attr.offset ? parseInt($attr.offset, 10) : duScrollOffset));
spyAPI.addSpy(spy);
$scope.$on('$destroy', function() {
spyAPI.removeSpy(spy);
});
$scope.$on('$locationChangeSuccess', spy.flushTargetCache.bind(spy));
$rootScope.$on('$stateChangeSuccess', spy.flushTargetCache.bind(spy));
}, 0, false);
}
};
}]);

View File

@ -2,10 +2,12 @@
#= require angular-cookies
#= require angular-resource
#= require angular-sanitize
#= require angular-scroll
#= require_self
#= require_tree ./angularjs
@app = angular.module('app', ['ngCookies', 'ngSanitize'])
@app = angular.module('app', ['ngCookies', 'ngSanitize', 'duScroll'])
@app.value('duScrollOffset', 30)
@app.config(["$httpProvider", (provider) ->
provider.defaults.headers.common['X-CSRF-Token'] = $('meta[name=csrf-token]').attr('content')

View File

@ -0,0 +1,14 @@
@app.controller 'AboutScrollController', [ '$scope', '$document', ($scope, $document)->
about_id = angular.element(document.getElementById('about'))
$document.on 'scroll', ()=>
$scope.$apply()
$scope.is_top = ()->
$document.scrollTop() <= 0
$scope.to_about = ()->
$document.scrollToElementAnimated(about_id)
]

View File

@ -3,6 +3,7 @@
//= require foundation/foundation
//= require foundation/foundation.topbar
//= require foundation/foundation.offcanvas
//= require foundation/foundation.magellan
//= require angularjs
//= require 'jquery.html5-fileupload'
//= require_tree .

View File

@ -20,3 +20,216 @@
}
}
}
.about-page {
@media only screen and (min-width: 40.063em) {
.top-bar-wrapper {
background: 0 0;
transition: background .5s ease-in-out,padding .5s ease-in-out;
&.active {
background: #000;
border-bottom: 1px solid #666;
.top-bar {
margin: 0.5rem 0;
}
}
.top-bar {
background: 0 0;
margin: 1.5rem 0;
.name h1 {
font-size: 1.325rem;
}
}
.top-bar-section ul{
li, li a {
background: 0 0;
font-size: 1rem;
}
li a:hover {
background: #666;
}
li a.active {
background: #4C4C4C;
}
}
}
}
p {
font-size: 1.275rem;
}
.intro {
//background-color: #7A7A7A;
background: image-url('intro-bg.jpg') no-repeat bottom center scroll;
background-size: cover;
height: 100%;
width: 100%;
display: table;
.intro-heading {
display: table-cell;
vertical-align: middle;
text-align: center;
.heading, .sub-heading {
color: #fff;
}
.sub-heading {
margin-top: 2rem;
p {
line-height: 2;
}
}
.circle {
color: #fff;
width: 4rem;
height: 4rem;
font-size: 3rem;
border: 2px solid #eee;
display: inline-block;
border-radius: 50%;
padding: 0.5rem 0;
margin-top: 2rem;
&:hover {
opacity: 0.7;
}
}
}
}
#about {
background-color: #000;
color: #eee;
padding: 10rem 0;
h1, h2 {
color: #eee;
text-align: center;
margin-bottom: 2rem;
}
}
#work {
background: image-url('download-bg.jpg') no-repeat bottom center scroll;
background-size: cover;
padding: 10rem 0;
h1, h2 {
color: #eee;
text-align: center;
margin-bottom: 2rem;
}
p {
color: #eee;
text-align: center;
}
.works {
color: #eee;
background-color: rgba(63, 63, 63, 0.6);
list-style: none;
padding: 2rem 1rem;
li {
margin: 1rem 0;
}
.name {
margin-right: 0.5rem;
font-size: 1.125rem;
font-weight: 600;
&:after {
margin-left: 0.5rem;
font-weight: 400;
content: '--';
}
}
.brief {
margin-right: 1rem;
color: #dfdfdf;
}
.link {
margin-right: 1rem;
color: #dfdfdf;
}
.time {
color: #aaa;
font-size: 95%;
}
}
}
#contact {
background: #000;
background-size: cover;
padding: 10rem 0;
padding-bottom: 20rem;
h1, h2 {
color: #eee;
text-align: center;
margin-bottom: 2rem;
}
p {
color: #eee;
text-align: center;
}
.mail_to {
margin-bottom: 3rem;
margin-top: 3rem;
}
.contact-ul {
text-align: center;
li {
display: inline-block;
}
li a {
display: inline-block;
padding: 0.5rem 2rem;
border: 1px solid #219AB3;
border-radius: 0.5rem;
color: #219AB3;
margin: 0 1rem;
transition: all .3s ease-in-out;
&:hover {
outline: 0;
color: #000;
background-color: #219ab3;
}
i {
margin-right: 0.5rem;
font-size: 1.1rem;
}
i.douban {
font-style: normal;
font-size: 95%;
vertical-align: baseline;
}
}
}
}
.footer {
border: none;
padding: 1.5rem;
background-color: #353535;
text-align: center;
color: #fff;
font-size: 1.125rem;
}
}

View File

@ -1190,3 +1190,4 @@ $base-font-size: 100%;
@import 'foundation';
@import 'foundation-icons';
@import 'font-awesome';

View File

@ -1,5 +1,97 @@
- content_for(:title) do
| 关于我
- content_for(:main) do
/! 导航
.about-page ng-app='app' ng-controller='AboutScrollController'
.top-bar-wrapper.contain-to-grid.fixed ng-class="{ active: ! is_top() }"
.row
.small-12.large-9.large-centered.columns.self-introduce
= render 'common/aboutme'
.small-12.columns
nav.top-bar data-topbar='' role='navigation'
ul.title-area
li.name
h1
a href='/' WinDy's Blog
section.top-bar-section
ul.right
li
a href='#about' du-smooth-scroll='' du-scrollspy='' 关于
li
a href='#work' du-smooth-scroll='' du-scrollspy='' 作品
li
a href='#contact' du-smooth-scroll='' du-scrollspy='' 联系
#intro
header.intro
.intro-heading
.row
.small-12.columns
h1.heading 李亚飞
.sub-heading
p
| 懒, 是人类进步的动力
br
| 所谓技术, 就是让你的生活越来越懒
a.circle href='#about' du-smooth-scroll=''
i.fa.fa-angle-double-down
section#about
.row
.small-12.large-9.large-centered.columns
h1.title 关于我
p 我是李亚飞, 一个在中国深圳的网页开发工程师( Web Developer ).
p
| 曾经在
a href='http://sangfor.com' 深信服
| 工作大约 5 年. 在那里, 从一个菜鸟成长为一个资深工程师, 还有幸带领一个很酷的团队帮助公司进行自动化测试方向的研究与推进. 掌握的技术大约有: Linux, Ruby, Ruby on Rails, Testing Automation
p
| 在今年 3 月份, 为了那份创业梦, 出山开发了 cywin.cn, 这是一个股权众筹平台, 帮助创业团队更好的融资. 借此, 也将自己的 Ruby on Rails 技术全部注入在内
p
| 现在, 想先休息一段时间, 接下来做一个自由职业者, 接手一些关于 Web 开发的外包工作
section#work
.row
.small-12.large-9.large-centered.columns
h1.title 作品
ul.works
li
span.name ATM/ATT
span.brief 测试业界领先的关键字驱动的自动化测试平台
span.link 公司所属
span.time #{time_ago_in_words(DateTime.new(2013))} 前
li
span.name WBlog
span.brief Rails 社区正缺了一个独立博客建站系统
span.link
a href='https://github.com/windy/wblog' target='_blank' https://github.com/windy/wblog
span.time #{time_ago_in_words(DateTime.new(2014,3,1))} 前
li
span.name Cywin
span.brief 一个股权众筹的商业平台
span.link
a href='http://cywin.cn' target='_blank' http://cywin.cn
span.time #{time_ago_in_words(1.month.ago)} 前
section#contact
.row
.small-12.large-9.large-centered.columns
h1.title 联系我
p
| 我目前处于自由职业状态, 如果你想给你的公司或个人制作一个精美简洁的网站, 或许我可以帮到你
p
| 不要紧张, 请随时联系我
p.mail_to
= mail_to 'lyfi2003@gmail.com'
ul.contact-ul
li
a href='https://github.com/windy' target='_blank'
i.fa.fa-github
| Github
li
a href='http://www.douban.com/people/41759170/' target='_blank'
i.douban
| 豆
| Douban
li
a href='http://www.linkedin.com/pub/yafei-lee/77/3b/505' target='_blank'
i.fa.fa-linkedin-square
| LinkedIn
.footer
.row
.small-12.columns
div Copyright © yafeilee.me

View File

@ -13,6 +13,9 @@ html
/ 增加 favico 图标
link href="/favicon.png" type='image/png' rel="icon"
body
- if content_for?(:main)
= yield(:main)
- else
.off-canvas-wrap data-offcanvas=''
.inner-wrap
nav.tab-bar