diff --git a/package.json b/package.json
index 516dcad..efc0db8 100644
--- a/package.json
+++ b/package.json
@@ -8,6 +8,8 @@
"scripts": {
"start": "rm -rf public && gulp",
"build": "rm -rf public && gulp build",
+ "dev": "SERVER=http://localhost:9000/ ENV=development npm start",
+ "prod": "ENV=production npm start",
"lint": "eslint src",
"test": "mocha src/**/__tests__/*.js --compilers js:babel-core/register --require test/test-helper"
},
@@ -22,8 +24,10 @@
"http-server": "^0.8.0",
"lodash": "^3.9.1",
"parse-github-event": "^0.2.0",
+ "prop-types": "^15.5.10",
"react": "^0.14.3",
"react-dom": "^0.14.3",
+ "react-stripe-checkout": "^2.4.0",
"timeago": "^0.2.0",
"twitter-text": "^1.11.0"
},
diff --git a/src/assets/images/ajax-loader.gif b/src/assets/images/ajax-loader.gif
deleted file mode 100644
index c35c951..0000000
Binary files a/src/assets/images/ajax-loader.gif and /dev/null differ
diff --git a/src/assets/images/hp3_bw.jpg b/src/assets/images/hp3_bw.jpg
new file mode 100644
index 0000000..c8ba56b
Binary files /dev/null and b/src/assets/images/hp3_bw.jpg differ
diff --git a/src/config.js b/src/config.js
new file mode 100644
index 0000000..84d5d3b
--- /dev/null
+++ b/src/config.js
@@ -0,0 +1,16 @@
+
+var development = {
+ stripe: {
+ publicKey: "pk_test_OmNve9H1OuORlmD4rblpjgzh"
+ }
+}
+
+var production = {
+ stripe: {
+ publicKey: "pk_live_xrnwdLNXbt20LMxpIDffJnnC"
+ }
+}
+
+module.exports = function () {
+ return process.env.ENV == 'development' ? development : production;
+}
diff --git a/src/index.jade b/src/index.jade
index 665b339..831583f 100644
--- a/src/index.jade
+++ b/src/index.jade
@@ -1,150 +1,79 @@
-doctype html
-html
- head
- title Koodiklinikka
- // inject:css
- // endinject
- meta(name='description', content='Koodiklinikka on suomalainen Slack-yhteisö ohjelmistoalan harrastajille ja ammattilaisille.')
- meta(name='keywords', content='ohjelmointi,frontend,open source,devaus,suomi,javascript,clojure,go,java,node.js,io.js,angular.js,web')
- meta(charset='utf-8')
- meta(name='viewport', content='width=device-width, initial-scale=1')
- meta(name='apple-mobile-web-app-capable', content='yes')
+extends templates/head
- script.
- if(location.hostname === 'koodiklinikka.fi' && location.protocol !== 'https:') {
- location.protocol = 'https';
- }
- link(rel='apple-touch-icon', sizes='57x57', href='icons/apple-touch-icon-57x57.png')
- link(rel='apple-touch-icon', sizes='114x114', href='icons/apple-touch-icon-114x114.png')
- link(rel='apple-touch-icon', sizes='72x72', href='icons/apple-touch-icon-72x72.png')
- link(rel='apple-touch-icon', sizes='144x144', href='icons/apple-touch-icon-144x144.png')
- link(rel='apple-touch-icon', sizes='60x60', href='icons/apple-touch-icon-60x60.png')
- link(rel='apple-touch-icon', sizes='120x120', href='icons/apple-touch-icon-120x120.png')
- link(rel='apple-touch-icon', sizes='76x76', href='icons/apple-touch-icon-76x76.png')
- link(rel='apple-touch-icon', sizes='152x152', href='icons/apple-touch-icon-152x152.png')
- link(rel='apple-touch-icon', sizes='180x180', href='icons/apple-touch-icon-180x180.png')
- link(rel='icon', type='image/png', href='icons/favicon-192x192.png', sizes='192x192')
- link(rel='icon', type='image/png', href='icons/favicon-160x160.png', sizes='160x160')
- link(rel='icon', type='image/png', href='icons/favicon-96x96.png', sizes='96x96')
- link(rel='icon', type='image/png', href='icons/favicon-16x16.png', sizes='16x16')
- link(rel='icon', type='image/png', href='icons/favicon-32x32.png', sizes='32x32')
- link(rel='shortcut icon', href='icons/favicon.ico')
- link(rel='icon', href='icons/favicon.ico')
- meta(name='msapplication-TileColor', content='#10558c')
- meta(name='msapplication-TileImage', content='icons/mstile-144x144.png')
- meta(property='og:image', content='images/logo.png')
- script(src='//use.typekit.net/scb5xny.js')
- script.
- try{Typekit.load();}catch(e){};
+block title
+ | Koodiklinikka
- body
- .site
- .container
- .header
- video(autoplay, loop, poster='images/poster.jpg', class='header__video-bg')
- source(src='videos/jumbo.mp4', type='video/mp4')
- .header__container
- .header__headline
- .header__logo
- h1.header__title Slack-yhteisö kaikille ohjelmoinnista ja ohjelmistoalasta kiinnostuneille harrastajille ja ammattilaisille.
+block header_content
+ video(autoplay, loop, poster='images/poster.jpg', class='header__video-bg')
+ source(src='videos/jumbo.mp4', type='video/mp4')
+ .header__container
+ .header__nav
+ a(href='/') etusivu
+ a(href='/yhdistys.html') yhdistys
+ .header__headline
+ .header__logo
+ h1.header__title Slack-yhteisö kaikille ohjelmoinnista ja ohjelmistoalasta kiinnostuneille harrastajille ja ammattilaisille.
- .content
- section
- .row
- h3 Tule mukaan
- #invite-form
+block content
+ .content.with-feed
+ section
+ .row
+ h3 Tule mukaan
+ #invite-form.form
- section
- .row
- .bread
- .column.column1-2
- h3 Yhteisö ohjelmoinnista kiinnostuneille
- p.
- Koodiklinikka on Suomen suurin ohjelmistoalan yhteisö, joka kokoaa työntekijät, harrastajat ja vasta-alkajat yhteen.
- Tarkoituksenamme on yhdistää ja kasvattaa suomalaista ohjelmointiyhteisöä, sekä tarjota apua ja uusia kontakteja ohjelmoinnista innostuneille nuorille.
- p.
- Mukaan liittyminen on ilmaista ja helppoa. Jätä sähköpostiosoitteesi ylläolevaan kenttään ja lähetämme sinulle kutsun Slack-yhteisöömme.
- .column.column1-2
- a(href='images/slack.png', target='_blank')
- img(src='images/slack.png')
+ section
+ .row
+ .bread
+ .column.column1-2
+ h3 Yhteisö ohjelmoinnista kiinnostuneille
+ p.
+ Koodiklinikka on Suomen suurin ohjelmistoalan yhteisö, joka kokoaa työntekijät, harrastajat ja vasta-alkajat yhteen.
+ Tarkoituksenamme on yhdistää ja kasvattaa suomalaista ohjelmointiyhteisöä, sekä tarjota apua ja uusia kontakteja ohjelmoinnista innostuneille nuorille.
+ p.
+ Mukaan liittyminen on ilmaista ja helppoa. Jätä sähköpostiosoitteesi ylläolevaan kenttään ja lähetämme sinulle kutsun Slack-yhteisöömme.
+ .column.column1-2
+ a(href='images/slack.png', target='_blank')
+ img(src='images/slack.png')
- .row
- .bread
- .column.column2-5
- img(src='images/octocat.png')
+ .row
+ .bread
+ .column.column2-5
+ img(src='images/octocat.png')
- .column.column3-5
- h3 Avoin lähdekoodi
- p
- |Suosimme avointa lähdekoodia ja kaikki käyttämämme koodi on vapaasti saatavilla ja hyödynnettävissä Github-organisaatiomme sivulta.
- |Organisaation jäseneksi otamme kaikki Slack-yhteisömme jäsenet ja kontribuutio projekteihimme otetaan lämpimästi vastaan.
+ .column.column3-5
+ h3 Avoin lähdekoodi
+ p
+ |Suosimme avointa lähdekoodia ja kaikki käyttämämme koodi on vapaasti saatavilla ja hyödynnettävissä Github-organisaatiomme sivulta.
+ |Organisaation jäseneksi otamme kaikki Slack-yhteisömme jäsenet ja kontribuutio projekteihimme otetaan lämpimästi vastaan.
- #members
+ #members
- .row
- h2 Potilaiden projekteja
- .bread
- .column.column2-5
- a(href='https://redom.js.org', target='_blank')
- img(src='images/redom.svg')
+ .row
+ h2 Potilaiden projekteja
+ .bread
+ .column.column2-5
+ a(href='https://redom.js.org', target='_blank')
+ img(src='images/redom.svg')
- .column.column3-5
- h4 RE:DOM
+ .column.column3-5
+ h4 RE:DOM
- p.
- Tiny (2 KB) turboboosted JavaScript library for creating user interfaces.
- Develop web apps with 100 % JavaScript and web standards.
+ p.
+ Tiny (2 KB) turboboosted JavaScript library for creating user interfaces.
+ Develop web apps with 100 % JavaScript and web standards.
- .bread
- .column.column2-5
- a(href='https://codestats.net/', target='_blank')
- img.project-image__codestats(src='images/codestats.png')
+ .bread
+ .column.column2-5
+ a(href='https://codestats.net/', target='_blank')
+ img.project-image__codestats(src='images/codestats.png')
- .column.column3-5
- h4 Code::Stats
+ .column.column3-5
+ h4 Code::Stats
- p.
- Code::Stats is a free stats tracking service for programmers
+ p.
+ Code::Stats is a free stats tracking service for programmers
- #members
+ #members
- #feed
-
-
- footer
- .sponsors
- .sponsors__label Yhteistyössä
- a(href='http://futurice.com/', target='_blank')
- img.sponsor.sponsor__futurice(src='images/futurice.svg')
- a(href='http://www.metosin.fi/', target='_blank')
- img.sponsor.sponsor__metosin(src='images/metosin.svg')
- a(href='https://www.solita.fi/', target='_blank')
- img.sponsor(src='images/solita.svg')
- a(href='http://leonidasoy.fi/', target='_blank')
- img.sponsor.sponsor__leonidas(src='images/leonidas.png')
- a(href='https://www.nordea.com/', target='_blank')
- img.sponsor.sponsor__nordea(src='images/nordea.png')
-
- .contacts
- div
- a(href='https://koodiklinikka.slack.com')
- i.fa.fa-slack
-
- a(href='https://github.com/koodiklinikka/koodiklinikka.fi')
- i.fa.fa-github
-
- a(href='https://twitter.com/koodiklinikka')
- i.fa.fa-twitter
-
- a(href='https://www.linkedin.com/groups/12025476')
- i.fa.fa-linkedin
-
- a(href='https://www.facebook.com/koodiklinikka')
- i.fa.fa-facebook
- div#email
-
-
- #fader
- // inject:js
- // endinject
+ #feed
diff --git a/src/js/components/inviteForm.js b/src/js/components/inviteForm.js
index 357419c..07317ea 100644
--- a/src/js/components/inviteForm.js
+++ b/src/js/components/inviteForm.js
@@ -5,7 +5,7 @@ var React = require('react');
var classSet = require('classnames');
var api = require('../api');
-
+var Loader = require('./loader');
module.exports = React.createClass({
getInitialState() {
return {
@@ -48,6 +48,7 @@ module.exports = React.createClass({
},
render() {
var formClasses = classSet({
+ 'form': true,
'invite-form': true,
'has-success': this.state.submitted,
'has-error': this.state.error,
@@ -76,7 +77,7 @@ module.exports = React.createClass({
}
feedbackMessage = (
-
+
{messageText}
);
@@ -98,11 +99,11 @@ module.exports = React.createClass({
disabled={this.state.error || this.state.submitted}>
⏎
-
-
+
+
+
{feedbackMessage}
- )
+ );
}
});
diff --git a/src/js/components/loader.js b/src/js/components/loader.js
new file mode 100644
index 0000000..80f58c2
--- /dev/null
+++ b/src/js/components/loader.js
@@ -0,0 +1,12 @@
+const React = require('react');
+
+module.exports = function Loader() {
+ return (
+
+ );
+};
diff --git a/src/js/components/members.js b/src/js/components/members.js
index e0e4013..a907016 100644
--- a/src/js/components/members.js
+++ b/src/js/components/members.js
@@ -7,7 +7,7 @@ var _ = require('lodash');
var api = require('../api');
module.exports = React.createClass({
- getInitialState() {
+ getInitialState() {
return {
members: []
};
diff --git a/src/js/components/membershipForm.js b/src/js/components/membershipForm.js
new file mode 100644
index 0000000..ceb30be
--- /dev/null
+++ b/src/js/components/membershipForm.js
@@ -0,0 +1,34 @@
+import React from 'react';
+import MembershipInfoForm from './membershipInfoForm';
+
+module.exports = React.createClass({
+ getInitialState() {
+ return {
+ paymentSuccess: false
+ };
+ },
+ handlePaymentSuccess() {
+ this.setState({ paymentSuccess: true });
+ },
+ render() {
+ if(!this.state.paymentSuccess) {
+ return (
+
+ );
+ }
+ return (
+
+
+
Maksu ja rekisteröityminen onnistui.
+
Tervetuloa Koodiklinikka ry:n jäseneksi!
+
+ );
+ }
+});
diff --git a/src/js/components/membershipInfoForm.js b/src/js/components/membershipInfoForm.js
new file mode 100644
index 0000000..185bdd8
--- /dev/null
+++ b/src/js/components/membershipInfoForm.js
@@ -0,0 +1,199 @@
+'use strict';
+
+var _ = require('lodash');
+var request = require('axios');
+var React = require('react');
+var classSet = require('classnames');
+var StripeCheckout = require('react-stripe-checkout').default;
+
+var api = require('../api');
+var Loader = require('./loader');
+var config = require('../../config.js')();
+
+var fieldNameTranslations = {
+ address: { fi: 'Osoite' },
+ city: { fi: 'Paikkakunta' },
+ email: { fi: 'Sähköpostiosoite' },
+ handle: { fi: 'Slack-käyttäjätunnus ' },
+ name: { fi: 'Koko nimi ' },
+ postcode: { fi: 'Postinumero' }
+};
+
+function validateEmail(email) {
+ var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
+ return re.test(email);
+}
+
+const fieldNames = ['name', 'email', 'handle', 'address', 'city', 'postcode'];
+
+function getUserInfo(state) {
+ return _.pick(state, fieldNames);
+}
+
+module.exports = React.createClass({
+
+ getInitialState() {
+ return {
+ address: '',
+ city: '',
+ email: '',
+ handle: '',
+ name: '',
+ postcode: '',
+ sending: false,
+ pristineFields: fieldNames
+ };
+ },
+ onSubmit(token) {
+ this.setState({
+ sending: true,
+ error: null
+ });
+
+ request.post(api('membership'), {
+ userInfo: getUserInfo(this.state),
+ stripeToken: token.id
+ })
+ .then(() => {
+ this.setState({ sending: false });
+ this.props.onPaymentSuccess();
+ })
+ .catch((err) => {
+ this.setState({ error: err, sending: false });
+ });
+ },
+ onChange(e) {
+ var name = e.target.name;
+ if (e.target.value === this.state[name]) {
+ return;
+ }
+
+ this.setState({
+ [e.target.name]: e.target.value,
+ pristineFields: this.state.pristineFields.filter((fieldName) => fieldName !== name),
+ errors: []
+ });
+ },
+
+ getDataErrors() {
+ var foundErrors = [];
+
+ fieldNames.forEach((fieldName) => {
+ if (!this.state[fieldName]) {
+ foundErrors.push({ field: fieldName, type: 'missing' });
+ }
+ });
+
+ if (this.state.email && !validateEmail(this.state.email)) {
+ foundErrors.push({ field: 'email', type: 'invalid' });
+ }
+
+ return foundErrors;
+ },
+
+ render() {
+ const inputErrors = this.getDataErrors();
+
+ var formClasses = classSet({
+ 'form': true,
+ 'membership-form': true,
+ 'has-error': inputErrors.length !== 0 || this.state.error,
+ 'sending': this.state.sending
+ });
+
+ function getErrorMessage(err) {
+ var feedbackText;
+
+ if (err.type === 'missing') {
+ feedbackText = `${fieldNameTranslations[err.field].fi} on pakollinen.`;
+ } else if (err.type === 'invalid') {
+ feedbackText = `${fieldNameTranslations[err.field].fi} on virheellinen.`;
+ }
+
+ return
{feedbackText}
;
+ }
+
+ /* generate error messages */
+ var visibleErrors = inputErrors
+ .filter((error) => this.state.pristineFields.indexOf(error.field) === -1);
+
+ var fieldsWithErrors = visibleErrors.map(({ field }) => field);
+
+ var inputFields = fieldNames.map((fieldName) => {
+ var inputClasses = classSet({
+ 'input': true,
+ 'has-error': _.includes(fieldsWithErrors, fieldName),
+ 'half': fieldName === 'city' || fieldName === 'postcode',
+ 'left': fieldName === 'city'
+ });
+
+ function showsErrorFor(field) {
+ if (fieldName === 'city') {
+ return false;
+ }
+
+ return field === fieldName || fieldName === 'postcode' && field === 'city';
+ }
+
+ return (
+
+
+ {
+ visibleErrors
+ .filter(({ field }) => showsErrorFor(field))
+ .map(getErrorMessage)
+
+ }
+
+ );
+ });
+ if (this.state.sending) {
+ return (
+
+
+
+ );
+ }
+ return (
+
+ );
+ }
+});
diff --git a/src/js/main.js b/src/js/main.js
index d5b35bc..ba585f8 100644
--- a/src/js/main.js
+++ b/src/js/main.js
@@ -3,31 +3,49 @@ require('./ga');
let ReactDOM = require('react-dom');
var React = require('react');
-
var inviteForm = React.createFactory(require('./components/inviteForm'));
var fader = React.createFactory(require('./components/fader'));
var members = React.createFactory(require('./components/members'));
var feed = React.createFactory(require('./components/feed'));
var email = React.createFactory(require('./components/email'));
+var membershipForm = React.createFactory(require('./components/membershipForm'));
-ReactDOM.render(
- inviteForm(),
- document.getElementById('invite-form'));
+const pathName = window.location.pathname;
-ReactDOM.render(
- fader(),
- document.getElementById('fader'));
+document.querySelectorAll('.email').forEach((element) =>
+ ReactDOM.render(email(), element)
+);
+if (pathName == '/') {
+ ReactDOM.render(
+ inviteForm(),
+ document.getElementById('invite-form'));
-ReactDOM.render(
- members(),
- document.getElementById('members'));
+ ReactDOM.render(
+ fader(),
+ document.getElementById('fader'));
+ ReactDOM.render(
+ members(),
+ document.getElementById('members'));
-ReactDOM.render(
- feed(),
- document.getElementById('feed'));
+ ReactDOM.render(
+ feed(),
+ document.getElementById('feed'));
-ReactDOM.render(
- email(),
- document.getElementById('email'));
+} else if (pathName == '/yhdistys.html') {
+ ReactDOM.render(
+ membershipForm(),
+ document.getElementById('membership-form'));
+
+ ReactDOM.render(
+ fader(),
+ document.getElementById('fader'));
+
+ ReactDOM.render(
+ React.createElement('div', {}, [
+ members({ key: 0 }),
+ members({ key: 1 })
+ ]),
+ document.getElementById('members'));
+}
diff --git a/src/styles/_header.styl b/src/styles/_header.styl
index 0bffafa..db74ade 100644
--- a/src/styles/_header.styl
+++ b/src/styles/_header.styl
@@ -1,8 +1,10 @@
+headerHeight = 400px
+
.header
background url('../images/jumbo.jpg')
background-position bottom center
background-size cover
- height 400px
+ height headerHeight
overflow hidden
position relative
width 100%
@@ -23,6 +25,43 @@
vertical-align middle
width 100%
+.header__nav
+ position absolute
+ right 60px
+ top 32px
+
+ @media screen and (max-width: 1030px)
+ right 40px
+ top 20px
+ a
+ color white
+ font-size 16px
+ font-weight bold
+ margin-left 16px
+ text-shadow 0 2px 0 rgba(0,0,0,0.1)
+ position relative
+ @media screen and (max-width: 420px)
+ display block
+ margin-top 5px
+
+ &:last-child
+ margin-left 1.5em
+ &:before
+ content '!'
+ font-size 11px
+ line-height 15px
+ width 14px
+ height 14px
+ text-align center
+ background #ec3d3d
+ display inline-block
+ margin-right 5px
+ top -5px
+ right -15px
+ border-radius 100%
+ position absolute
+ text-shadow none
+
.header__headline
display table-cell
padding 0 1em
@@ -85,3 +124,59 @@
@media (min-aspect-ratio: 1/2)
width 100%
height auto
+
+@keyframes spin
+ 0%
+ top 0
+ 50%
+ top -(headerHeight)
+ 50.0001%
+ top headerHeight
+ 100%
+ top 0
+
+@keyframes spin2
+ 0%
+ top (headerHeight)
+ 50%
+ top 0
+ 99.99999%
+ top -(headerHeight)
+ 100%
+ top (headerHeight)
+
+.header__members
+ width 100%
+ height 100%
+ z-index -1
+ position absolute
+ top 0
+ left 0
+
+ .member
+ margin 0
+ border-radius 0
+ width (100/18)%
+
+ @media screen and (min-width: 2000px)
+ width 5%
+ @media screen and (max-width: 1200px)
+ width (100/15)%
+ @media screen and (max-width: 810px)
+ width 10%
+ @media screen and (max-width: 450px)
+ width 20%
+
+ .members
+ position absolute
+ overflow hidden
+ top 0
+ left 0
+ right 0
+ height headerHeight
+ animation spin 40s infinite linear
+ &:first-child
+ z-index 1
+ &:last-child
+ animation spin2 40s infinite linear
+
diff --git a/src/styles/_input.styl b/src/styles/_input.styl
index 36a182d..808e2f8 100644
--- a/src/styles/_input.styl
+++ b/src/styles/_input.styl
@@ -30,3 +30,8 @@
.input.has-error
border-color rgba(226, 33, 112, 0.6)
color rgb(226, 33, 112)
+
+.input.half
+ width 48%
+ &.left
+ margin-right 4%
diff --git a/src/styles/_loader.styl b/src/styles/_loader.styl
new file mode 100644
index 0000000..71eb8f5
--- /dev/null
+++ b/src/styles/_loader.styl
@@ -0,0 +1,55 @@
+.sk-folding-cube
+ margin auto
+ width 100%
+ height 100%
+ position relative
+ transform rotateZ(45deg)
+
+.sk-folding-cube .sk-cube
+ float left
+ width 50%
+ height 50%
+ position relative
+ transform scale(1.1)
+
+.sk-folding-cube .sk-cube:before
+ content ''
+ position absolute
+ top 0
+ left 0
+ width 100%
+ height 100%
+ background-color linkColor
+ animation sk-foldCubeAngle 2.4s infinite linear both
+ transform-origin 100% 100%
+
+.sk-folding-cube .sk-cube2
+ transform scale(1.1) rotateZ(90deg)
+
+.sk-folding-cube .sk-cube3
+ transform scale(1.1) rotateZ(180deg)
+
+.sk-folding-cube .sk-cube4
+ transform scale(1.1) rotateZ(270deg)
+
+.sk-folding-cube .sk-cube2:before
+ animation-delay 0.3s
+
+.sk-folding-cube .sk-cube3:before
+ animation-delay 0.6s
+
+.sk-folding-cube .sk-cube4:before
+ animation-delay 0.9s
+
+@keyframes sk-foldCubeAngle
+ 0%, 10%
+ transform perspective(140px) rotateX(-180deg)
+ opacity 0
+ 25%, 75%
+ transform perspective(140px) rotateX(0deg)
+ opacity 1
+ 90%, 100%
+ transform perspective(140px) rotateY(180deg)
+ opacity 0
+
+
diff --git a/src/styles/style.styl b/src/styles/style.styl
index c1a6b38..1b1747f 100644
--- a/src/styles/style.styl
+++ b/src/styles/style.styl
@@ -6,6 +6,7 @@ footerHeight = 50px
@require '_input'
@require '_button'
@require '_header'
+@require '_loader'
body, html
margin 0
@@ -25,6 +26,9 @@ h1, h2, h3
h2
margin-bottom 1em
+h3
+ font-size 1.25em
+
p
margin-top 1em
line-height 1.5em
@@ -51,13 +55,17 @@ section
.content
z-index 2
position relative
- padding-right feedWidth
min-height 50vh
box-sizing border-box
@media screen and (max-width: 700px)
h3
margin-top 0
+
+ &.with-feed
+ padding-right feedWidth
+
+
section:first-child
box-shadow -1px -1px 1px rgba(0, 0, 0, 0.05)
border-bottom 1px solid #EEEEEE
@@ -104,35 +112,55 @@ section:first-child
padding 0
&:first-child
margin-top 0
+
+.form
+ .btn
+ background linkColor
+ border-bottom 2px solid #117280
+ color rgba(255, 255, 255, 0.9)
+ &.sending
+ .btn
+ display none
+ .invite-form__loader
+ display block
.invite-form
position relative
.btn
width 40px
height 32px
- background linkColor
padding 0
position absolute
right 8px
top 7px
-
- border-bottom 2px solid #117280
color rgba(255, 255, 255, 0.5)
&:active
border-bottom 0
- .loader
+ .invite-form__loader
+ display none
+ width 20px
+ height 20px
position absolute
- right 9px
- top 9px
- width 0px
- height 0px
- background transparent url('../images/ajax-loader.gif') no-repeat center center
+ right 14px
+ top 14px
+
+.membership-form
+ .input
+ margin 8px 0px
+ .btn
+ margin-top 12px
+
+.membership-form__loader
+ width 70px
+ height 70px
+ margin auto
+
+.stripe-form
+ margin 20px 0px
+ .name
+ margin-top 20px
+ text-align left
display block
- &.sending
- .btn
- display none
- .loader
- width 28px
- height 28px
+ color rgba(0, 0, 0, 0.4)
@keyframes drop
0%
@@ -146,7 +174,7 @@ section:first-child
100%
transform rotateX(0deg)
-.invite-form--message
+.form--message
background linkColor
color #fff
line-height 40px
@@ -156,8 +184,8 @@ section:first-child
transform-origin 100% 0
animation drop 0.6s linear
-.invite-form.has-error
- .invite-form--message
+.form.has-error
+ .form--message
background rgb(226, 33, 112)
.members
@@ -182,7 +210,7 @@ footer
justify-content space-between
flex-wrap wrap
text-align center
- @media screen and (max-width: 760px)
+ @media screen and (max-width: 940px)
display block
i
margin 0 0.30em
@@ -194,7 +222,7 @@ footer
display flex
flex-direction column
justify-content center
- @media screen and (max-width: 760px)
+ @media screen and (max-width: 940px)
margin-top 1em
.sponsors
@@ -215,16 +243,17 @@ footer
height 40px
margin-right 1em
vertical-align middle
- @media screen and (max-width: 760px)
+ @media screen and (max-width: 940px)
margin-top 1em
-
.sponsor__futurice, .sponsor__metosin, .sponsor__leonidas
height 30px
.sponsor__nordea
height 25px
- margin-top: -3px
+ margin-top -3px
+ @media screen and (max-width: 940px)
+ margin-top 1em
.feed
width feedWidth
@@ -242,7 +271,7 @@ footer
@media screen and (max-width: 980px)
.feed
width 0
- .content
+ .content.with-feed
padding-right 0
.message
@@ -314,3 +343,24 @@ footer
display block
.column
display block
+
+.bread-img
+ background url('../images/hp3_bw.jpg')
+ background-size cover
+ border-radius 160px
+ opacity 0.85
+ width 320px
+ height 320px
+ margin auto
+
+@media screen and (max-width: 700px)
+ .bread-img
+ display none
+
+.organization
+ padding-top 3em
+
+.membership-information.column
+ vertical-align initial
+ p:first-child
+ margin-top 37px
diff --git a/src/templates/head.jade b/src/templates/head.jade
new file mode 100644
index 0000000..b714c52
--- /dev/null
+++ b/src/templates/head.jade
@@ -0,0 +1,84 @@
+doctype html
+html
+ head
+ title
+ block title
+ // inject:css
+ // endinject
+ meta(name='description', content='Koodiklinikka on suomalainen Slack-yhteisö ohjelmistoalan harrastajille ja ammattilaisille.')
+ meta(name='keywords', content='ohjelmointi,frontend,open source,devaus,suomi,javascript,clojure,go,java,node.js,io.js,angular.js,web')
+ meta(charset='utf-8')
+ meta(name='viewport', content='width=device-width, initial-scale=1')
+ meta(name='apple-mobile-web-app-capable', content='yes')
+
+ script.
+ if(location.hostname === 'koodiklinikka.fi' && location.protocol !== 'https:') {
+ location.protocol = 'https';
+ }
+ link(rel='apple-touch-icon', sizes='57x57', href='icons/apple-touch-icon-57x57.png')
+ link(rel='apple-touch-icon', sizes='114x114', href='icons/apple-touch-icon-114x114.png')
+ link(rel='apple-touch-icon', sizes='72x72', href='icons/apple-touch-icon-72x72.png')
+ link(rel='apple-touch-icon', sizes='144x144', href='icons/apple-touch-icon-144x144.png')
+ link(rel='apple-touch-icon', sizes='60x60', href='icons/apple-touch-icon-60x60.png')
+ link(rel='apple-touch-icon', sizes='120x120', href='icons/apple-touch-icon-120x120.png')
+ link(rel='apple-touch-icon', sizes='76x76', href='icons/apple-touch-icon-76x76.png')
+ link(rel='apple-touch-icon', sizes='152x152', href='icons/apple-touch-icon-152x152.png')
+ link(rel='apple-touch-icon', sizes='180x180', href='icons/apple-touch-icon-180x180.png')
+ link(rel='icon', type='image/png', href='icons/favicon-192x192.png', sizes='192x192')
+ link(rel='icon', type='image/png', href='icons/favicon-160x160.png', sizes='160x160')
+ link(rel='icon', type='image/png', href='icons/favicon-96x96.png', sizes='96x96')
+ link(rel='icon', type='image/png', href='icons/favicon-16x16.png', sizes='16x16')
+ link(rel='icon', type='image/png', href='icons/favicon-32x32.png', sizes='32x32')
+ link(rel='shortcut icon', href='icons/favicon.ico')
+ link(rel='icon', href='icons/favicon.ico')
+ meta(name='msapplication-TileColor', content='#10558c')
+ meta(name='msapplication-TileImage', content='icons/mstile-144x144.png')
+ meta(property='og:image', content='images/logo.png')
+ script(src='https://js.stripe.com/v3/')
+ script(src='//use.typekit.net/scb5xny.js')
+ script.
+ try{Typekit.load();}catch(e){};
+
+ body
+ .site
+ .container
+ .header
+ block header_content
+
+ block content
+
+ footer
+ .sponsors
+ .sponsors__label Yhteistyössä
+ a(href='http://futurice.com/', target='_blank')
+ img.sponsor.sponsor__futurice(src='images/futurice.svg')
+ a(href='http://www.metosin.fi/', target='_blank')
+ img.sponsor.sponsor__metosin(src='images/metosin.svg')
+ a(href='https://www.solita.fi/', target='_blank')
+ img.sponsor(src='images/solita.svg')
+ a(href='http://leonidasoy.fi/', target='_blank')
+ img.sponsor.sponsor__leonidas(src='images/leonidas.png')
+ a(href='https://www.nordea.com/', target='_blank')
+ img.sponsor.sponsor__nordea(src='images/nordea.png')
+
+ .contacts
+ div
+ a(href='https://koodiklinikka.slack.com')
+ i.fa.fa-slack
+
+ a(href='https://github.com/koodiklinikka/koodiklinikka.fi')
+ i.fa.fa-github
+
+ a(href='https://twitter.com/koodiklinikka')
+ i.fa.fa-twitter
+
+ a(href='https://www.linkedin.com/groups/12025476')
+ i.fa.fa-linkedin
+
+ a(href='https://www.facebook.com/koodiklinikka')
+ i.fa.fa-facebook
+ div#email.email
+
+ #fader
+ // inject:js
+ // endinject
diff --git a/src/yhdistys.jade b/src/yhdistys.jade
new file mode 100644
index 0000000..028541a
--- /dev/null
+++ b/src/yhdistys.jade
@@ -0,0 +1,48 @@
+extends templates/head
+
+block title
+ | Yhdistys
+
+block header_content
+ .header__container
+ .header__nav
+ a(href='/') etusivu
+ a(href='/yhdistys.html') yhdistys
+ .header__headline
+ .header__logo
+ h1.header__title Koodiklinikka ry
+ .header__members
+ #members
+
+block content
+ .content
+ section.organization
+ .row
+ .bread
+ .column.column1-2
+ h3 Rekisteröity yhdistys
+ p.
+ Koodiklinikka on nyt rekisteröity yhdistys!
+ p.
+ Lähes nelivuotisen taipaleensa aikana Koodiklinikka on kasvanut lähes tuhannen rekisteröityneen jäsenen yhteisöiksi ja näin saavuttanut aseman Suomen suurimpana ohjelmointiaiheisena yhteisönä! Liittymällä Koodiklinikka ry:n jäseneksi tuet toimintaamme, ja tulevaisuudessa yhdistyksen jäsenyys oikeuttaa etuihin Koodiklinikan tapahtumissa.
+ p.
+ Yhdistyksen jäsenyys
ei ole, eikä tule olemaan,
pakollinen Koodiklinikan käyttäjille tai meetuppeihin osallistujille.
+ .column.column1-2
+ .bread-img
+ .row
+ .bread
+ .column.column1-2
+ #membership-form.form
+
+ .column.column1-2.membership-information
+ p.
+ Kuka tahansa Slack:iin rekisteröitynyt käyttäjä voi liittyä yhdistyksen jäseneksi maksamalla
10€ vuosittaisen jäsenmaksun.
+ p.
+ Jäsenmaksulla tulemme tekemään tapahtumistamme vieläkin mielenkiintoisempia ja kattamaan kustannuksia jäsenmäärämme kasvaessa.
+ p.
+ Jos tarvitset Slack-kutsun, siirry
etusivulle.
+ p.
+ Jäsenrekisterin rekisteriseloste luettavissa
Google Drivessä.
+ p.
+ Lisätietoa yhdistyksen jäsenyydestä tai vuosimaksusta emaililla
+