feat: new design for koodiklinikka.fi
Co-authored-by: Aarni Koskela <akx@iki.fi>
36
.eslintrc.js
@@ -1,37 +1,3 @@
|
||||
module.exports = {
|
||||
extends: [
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:react-hooks/recommended",
|
||||
"plugin:react/recommended",
|
||||
"plugin:jsx-a11y/recommended",
|
||||
"plugin:@next/next/recommended",
|
||||
"prettier",
|
||||
],
|
||||
settings: {
|
||||
react: {
|
||||
version: "detect",
|
||||
},
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
node: true,
|
||||
},
|
||||
parser: "@typescript-eslint/parser",
|
||||
parserOptions: {
|
||||
ecmaVersion: 2020,
|
||||
sourceType: "module",
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
"@typescript-eslint/ban-types": "warn",
|
||||
"no-use-before-define": 0,
|
||||
"padded-blocks": 0,
|
||||
"react/jsx-no-target-blank": 0,
|
||||
"react/jsx-uses-react": 2,
|
||||
"react/jsx-uses-vars": 2,
|
||||
"react/prop-types": 0,
|
||||
"react/react-in-jsx-scope": 2,
|
||||
},
|
||||
extends: 'next/core-web-vitals',
|
||||
};
|
||||
|
||||
14
.gitignore
vendored
@@ -1,7 +1,17 @@
|
||||
*.log
|
||||
*.log*
|
||||
*.pem
|
||||
*.tsbuildinfo
|
||||
.DS_Store
|
||||
.env*.local
|
||||
.history
|
||||
.next
|
||||
.pnp.js
|
||||
.vercel
|
||||
.yarn
|
||||
/.pnp
|
||||
/build
|
||||
/coverage
|
||||
next-env.d.ts
|
||||
node_modules
|
||||
out
|
||||
package-lock.json
|
||||
package-lock.json
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Riku Rouvila
|
||||
Copyright (c) 2024 Petri Partio
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
13
README.md
@@ -2,7 +2,7 @@
|
||||
|
||||

|
||||
|
||||
<img align="right" src="./public/static/images/logo-new-black.svg" alt="Koodiklinikka-logo" max-width="30%">
|
||||
<img align="right" src="./logo-new-black.svg" alt="Koodiklinikka-logo" max-width="30%">
|
||||
|
||||
**Koodiklinikka.fi lähdekoodi**. [Issueita](https://github.com/koodiklinikka/koodiklinikka.fi/issues) ja [Pull Requestejä](https://github.com/koodiklinikka/koodiklinikka.fi/pulls) otetaan lämpimästi vastaan. Yritämme pitää kynnyksen kontribuoida projektiin alhaisena, jotta mahdollisimman moni pääsisi jättämään siihen jälkensä. Kaikki koodi katselmoidaan läpi ja mergetään projektiin kun näyttää hyvälle. Muutamasta mergetystä Pull Requestista oikeudet ylläpitää projektia.
|
||||
|
||||
@@ -20,8 +20,7 @@
|
||||
|
||||
### Vaaditut työkalut
|
||||
|
||||
- Asenna [Node.js](http://nodejs.org)
|
||||
- Asenna [Yarn 1.x](https://classic.yarnpkg.com/en/)
|
||||
- Asenna [Node.js](http://nodejs.org) ja [Yarn 1.x](https://classic.yarnpkg.com/en/) (tai [Bun](https://bun.sh/))
|
||||
- Asenna [Git](https://git-scm.com/) client lähdekoodin hallintaan
|
||||
|
||||
### Kloonaa projekti koneellesi
|
||||
@@ -44,14 +43,14 @@ Avaa selaimessasi: [`http://localhost:3000`](http://localhost:3000)
|
||||
|
||||
## Komennot
|
||||
|
||||
### `yarn`
|
||||
### `yarn` (tai `bun i`)
|
||||
|
||||
Asentaa projektin riippuvuudet
|
||||
|
||||
### `yarn start`
|
||||
### `yarn dev` (tai `bun dev`)
|
||||
|
||||
Kääntää lähdetiedostot ja palvelee sovellusta porttiin `3000`
|
||||
Palvelee sovellusta kehitystilassa porttiin `3000`
|
||||
|
||||
### `yarn build`
|
||||
### `yarn build` (tai `bun run build`)
|
||||
|
||||
Kääntää lähdetiedostot -> `out/` -hakemistoon
|
||||
|
||||
99
app/globals.css
Normal file
@@ -0,0 +1,99 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer utilities {
|
||||
.text-balance {
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
.text-shadow {
|
||||
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.title-highlight {
|
||||
background: linear-gradient(200deg, #ff0098 20%, #0ef 80%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
filter: drop-shadow(0 0 20px rgba(255, 0, 234, 0.2));
|
||||
}
|
||||
|
||||
@supports (color: color(display-p3 1 1 1)) {
|
||||
.title-highlight {
|
||||
background: linear-gradient(200deg, oklch(68% 0.5 340) 20%, oklch(90% 0.5 200) 100%);
|
||||
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
filter: drop-shadow(0 0 20px oklch(80% 0.41 211 / 20%));
|
||||
}
|
||||
}
|
||||
|
||||
.bg-button {
|
||||
background: linear-gradient(200deg, #f0f 20%, #ff00c4 100%);
|
||||
}
|
||||
|
||||
@supports (color: color(display-p3 1 1 1)) {
|
||||
.bg-button {
|
||||
background: linear-gradient(200deg, oklch(100% 0.5 340) 20%, oklch(86% 0.5 360) 100%);
|
||||
}
|
||||
}
|
||||
|
||||
html {
|
||||
background: #070b1e url('../public/background.webp');
|
||||
background-size: 1024px auto;
|
||||
background-position: top center;
|
||||
background-repeat: no-repeat;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
html {
|
||||
background-size: 100% auto;
|
||||
}
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3 {
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
.checkbox svg {
|
||||
display: none;
|
||||
}
|
||||
|
||||
input[type='checkbox']:checked + .checkbox {
|
||||
background-color: #ef008e;
|
||||
border-color: #ff0099;
|
||||
}
|
||||
|
||||
input[type='checkbox']:checked + .checkbox svg {
|
||||
display: block;
|
||||
align-items: center;
|
||||
justify-items: center;
|
||||
width: 80%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
input[type='checkbox']:focus + .checkbox {
|
||||
outline: 2px solid var(--tw-color-red-800);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
@keyframes fadeInOut {
|
||||
0% {
|
||||
opacity: 20%;
|
||||
}
|
||||
50% {
|
||||
opacity: 100%;
|
||||
}
|
||||
100% {
|
||||
opacity: 20%;
|
||||
}
|
||||
}
|
||||
|
||||
.fade-in-out {
|
||||
opacity: 20%;
|
||||
animation: fadeInOut 4s ease-in-out infinite;
|
||||
}
|
||||
BIN
app/icon.png
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
27
app/layout.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import type { Metadata } from 'next';
|
||||
import { Inter } from 'next/font/google';
|
||||
import './globals.css';
|
||||
import BottomFade from '@/components/BottomFade';
|
||||
|
||||
const inter = Inter({ subsets: ['latin'] });
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Koodiklinikka',
|
||||
description: 'Yhteisö kaikille ohjelmoinnista ja ohjelmistoalasta kiinnostuneille harrastajille ja ammattilaisille',
|
||||
robots: 'noindex',
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="fi">
|
||||
<body className={`${inter.className} pb-24 text-white`}>
|
||||
{children}
|
||||
<BottomFade />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
1
app/opengraph-image.alt.txt
Normal file
@@ -0,0 +1 @@
|
||||
Koodiklinikka on Suomen suurin ohjelmistoalan yhteisö, joka tuo alan ammattilaiset ja harrastajat yhteen
|
||||
BIN
app/opengraph-image.jpg
Normal file
|
After Width: | Height: | Size: 70 KiB |
105
app/page.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
import shuffle from 'lodash.shuffle';
|
||||
|
||||
import ChannelGrid from '@/components/ChannelGrid';
|
||||
import FeatureImage from '@/components/FeatureImage';
|
||||
import Footer from '@/components/Footer';
|
||||
import Hero from '@/components/Hero';
|
||||
import Nav from '@/components/Nav';
|
||||
import Wrapper from '@/components/Wrapper';
|
||||
|
||||
async function getChannels() {
|
||||
const res = await fetch('https://stats.koodiklinikka.fi/api/channels', { next: { revalidate: 3600 } });
|
||||
|
||||
if (!res.ok) {
|
||||
// This will activate the closest `error.js` Error Boundary
|
||||
throw new Error('Failed to fetch data');
|
||||
}
|
||||
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export default async function Home() {
|
||||
let channels: Channel[] = await getChannels();
|
||||
channels = channels.sort((a, b) => (a.messages_today > b.messages_today ? -1 : 1));
|
||||
|
||||
return (
|
||||
<>
|
||||
<Nav />
|
||||
<main className="mt-20">
|
||||
<Wrapper>
|
||||
<Hero />
|
||||
|
||||
<div className="text-shadow py-16 lg:my-24">
|
||||
<h2 className="mx-auto max-w-screen-xs text-center text-2xl font-extrabold md:max-w-none md:text-3xl">
|
||||
Suosituimmat keskustelunaiheet tänään
|
||||
</h2>
|
||||
|
||||
<ChannelGrid channels={channels.splice(0, 12)} />
|
||||
|
||||
<div className="mx-auto max-w-md p-6 text-center font-mono text-sm leading-relaxed text-fuchsia-100/60 lg:max-w-3xl">
|
||||
Ja paljon muuta:{' '}
|
||||
{shuffle(channels.splice(0, 20))
|
||||
.map<React.ReactNode>((channel) => (
|
||||
<a
|
||||
key={channel.id}
|
||||
href={`https://app.slack.com/client/T03BQ3NU9/${channel.id}`}
|
||||
target="_blank"
|
||||
className="underline-offset-4 hover:underline"
|
||||
>
|
||||
#{channel.name}
|
||||
</a>
|
||||
))
|
||||
.reduce((prev, curr) => [prev, ', ', curr])}
|
||||
…
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mx-auto max-w-lg space-y-14 p-6 md:p-12 lg:max-w-none lg:space-y-28">
|
||||
<div className="text-shadow grid gap-10 lg:grid-cols-2 lg:gap-16">
|
||||
<div className="lg:order-2">
|
||||
<FeatureImage src="/meetup.webp" alt="" width="1792" height="1024" />
|
||||
</div>
|
||||
<div className="flex flex-col justify-center space-y-5 lg:order-1">
|
||||
<h2 className="text-3xl font-extrabold">Yhteisö ohjelmoinnista kiinnostuneille</h2>
|
||||
<div className="space-y-5 font-mono text-sm leading-relaxed text-pink-100">
|
||||
<p>
|
||||
Koodiklinikka on Suomen suurin ohjelmistoalan yhteisö, joka kokoaa yhteen ammattilaiset, harrastajat
|
||||
ja vasta-alkajat. Tavoitteenamme on yhdistää ja kasvattaa suomalaista ohjelmointiyhteisöä sekä
|
||||
tarjota apua ja uusia kontakteja kaikille ohjelmoinnista innostuneille.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Liittyminen on ilmaista ja helppoa. Jätä sähköpostiosoitteesi{' '}
|
||||
<a href="#liity" className="underline underline-offset-4">
|
||||
yllä olevaan kenttään
|
||||
</a>
|
||||
, niin lähetämme sinulle kutsun Slack-yhteisöömme.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-shadow grid gap-10 lg:grid-cols-2 lg:gap-16">
|
||||
<div>
|
||||
<FeatureImage src="/opensource.webp" alt="" width="1792" height="1024" />
|
||||
</div>
|
||||
<div className="flex flex-col justify-center space-y-5">
|
||||
<h2 className="text-3xl font-extrabold">Avoin lähdekoodi <3</h2>
|
||||
<div className="space-y-5 font-mono text-sm leading-relaxed text-pink-100">
|
||||
<p>
|
||||
Suosimme avointa lähdekoodia ja kaikki käyttämämme koodi on vapaasti saatavilla sekä
|
||||
hyödynnettävissä Github-organisaatiomme sivulta. Organisaation jäseneksi otamme kaikki
|
||||
Slack-yhteisömme jäsenet. Koodiklinikan projekteihin voi osallistua kuka tahansa ja muutosideat ovat
|
||||
aina lämpimästi tervetulleita!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Wrapper>
|
||||
</main>
|
||||
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
}
|
||||
1
app/twitter-image.alt.txt
Normal file
@@ -0,0 +1 @@
|
||||
Koodiklinikka on Suomen suurin ohjelmistoalan yhteisö, joka tuo alan ammattilaiset ja harrastajat yhteen
|
||||
BIN
app/twitter-image.jpg
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
assets/banner.png
Normal file
|
After Width: | Height: | Size: 494 KiB |
5
components/BottomFade.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
export default function BottomFade() {
|
||||
return (
|
||||
<div className="pointer-events-none fixed bottom-0 left-0 right-0 z-50 h-32 bg-gradient-to-t from-[#070b1e] to-black/0"></div>
|
||||
);
|
||||
}
|
||||
33
components/ChannelGrid.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import shuffle from 'lodash.shuffle';
|
||||
|
||||
const DELAYS = shuffle([0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1]);
|
||||
|
||||
export default function ChannelGrid({ channels }: { channels: Channel[] }) {
|
||||
return (
|
||||
<div className="mt-8 grid gap-3 rounded-3xl bg-gradient-to-b from-black/10 to-black/0 p-6 backdrop-blur-sm xs:grid-cols-2 sm:grid-cols-3 md:grid-cols-4 md:p-12">
|
||||
{channels.map((channel, i) => (
|
||||
<div key={channel.id} className="relative h-[5.5rem]">
|
||||
<div
|
||||
className="fade-in-out absolute bottom-0 left-0 right-0 top-0 z-0 rounded-[9px] bg-fuchsia-200/40"
|
||||
style={{
|
||||
WebkitMask: 'linear-gradient(to bottom, rgba(0,0,0,1), rgba(0,0,0,0))',
|
||||
animationDelay: `${DELAYS[i] * 2}s`,
|
||||
}}
|
||||
></div>
|
||||
<div
|
||||
className="absolute bottom-0 left-0 right-0 top-0 z-10 m-px rounded-[8px] bg-[#2c0c33]"
|
||||
style={{ WebkitMask: 'linear-gradient(to bottom, rgba(0,0,0,.9), rgba(0,0,0,0))' }}
|
||||
></div>
|
||||
<div className="relative z-20 flex flex-col items-center justify-center gap-1 px-3 py-6 font-mono">
|
||||
<a
|
||||
href={`https://app.slack.com/client/T03BQ3NU9/${channel.id}`}
|
||||
target="_blank"
|
||||
className="text-sm font-semibold underline-offset-4 hover:underline"
|
||||
>{`#${channel.name}`}</a>
|
||||
<div className="text-xs opacity-70">{channel.num_members} jäsentä</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/ban-types */
|
||||
import React from "react";
|
||||
|
||||
function renderStringWithChannelRefs(value: string) {
|
||||
return (
|
||||
<>
|
||||
{value.split(/(<#[A-Z0-9]+\|[A-Za-z0-9]+>)/).map((str, i) => {
|
||||
const matches = str.match(/<#([A-Z0-9]+)\|([A-Za-z0-9]+)>/);
|
||||
if (matches) {
|
||||
return (
|
||||
<a
|
||||
href={`https://app.slack.com/client/T03BQ3NU9/${matches[1]}`}
|
||||
key={i}
|
||||
>
|
||||
#{matches[2]}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
return <React.Fragment key={i}>{str}</React.Fragment>;
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export const ChannelReferenceRenderer = ({
|
||||
children,
|
||||
}: React.PropsWithChildren<{}>) => {
|
||||
// TODO: this should probably walk the tree
|
||||
if (typeof children[0] === "string")
|
||||
return renderStringWithChannelRefs(children[0]);
|
||||
return <>{children}</>;
|
||||
};
|
||||
@@ -1,5 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
export default function EmailComponent() {
|
||||
return <a href="mailto:info@koodiklinikka.fi">info@koodiklinikka.fi</a>;
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
type Props = {
|
||||
threshold: number;
|
||||
};
|
||||
|
||||
function clamp(min, max, value) {
|
||||
return Math.min(Math.max(value, min), max);
|
||||
}
|
||||
|
||||
export default class Fader extends React.Component<Props> {
|
||||
static defaultProps = {
|
||||
threshold: 100,
|
||||
};
|
||||
|
||||
state = {
|
||||
opacity: 0,
|
||||
};
|
||||
|
||||
onScroll = () => {
|
||||
const scrollableDistance = document.body.scrollHeight - window.innerHeight,
|
||||
scrollTop = window.pageYOffset || document.documentElement.scrollTop,
|
||||
distanceToBottom = scrollableDistance - scrollTop;
|
||||
|
||||
this.setState({
|
||||
opacity: clamp(0, 1, distanceToBottom / this.props.threshold),
|
||||
});
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
window.addEventListener("scroll", this.onScroll);
|
||||
this.onScroll();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener("scroll", this.onScroll);
|
||||
}
|
||||
|
||||
render() {
|
||||
const style = {
|
||||
opacity: this.state.opacity,
|
||||
};
|
||||
|
||||
return <div className="fader" style={style}></div>;
|
||||
}
|
||||
}
|
||||
27
components/FeatureImage.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import Image from 'next/image';
|
||||
|
||||
export default function FeatureImage({
|
||||
src,
|
||||
width,
|
||||
height,
|
||||
alt,
|
||||
}: {
|
||||
src: string;
|
||||
width: number | `${number}`;
|
||||
height: number | `${number}`;
|
||||
alt: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="relative rounded-[18px] p-px shadow-2xl">
|
||||
<div
|
||||
className="fade-in-out absolute bottom-0 left-0 right-0 top-0 z-0 rounded-[17px] bg-gradient-to-tr from-[#4d094e] to-pink-500/70 p-[2px] "
|
||||
style={{
|
||||
WebkitMask: 'linear-gradient(to bottom, rgba(0,0,0,1), rgba(0,0,0,0))',
|
||||
animationDelay: `${Math.floor(Math.random() * 5) + 1 * 0.5}s`,
|
||||
}}
|
||||
></div>
|
||||
|
||||
<Image className="relative z-10 block rounded-2xl " src={src} alt={alt} width={width} height={height} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
import flatMap from "lodash/flatMap";
|
||||
import sortBy from "lodash/sortBy";
|
||||
import React from "react";
|
||||
import request from "axios";
|
||||
import api from "./api";
|
||||
import transformers from "./feed-transformers";
|
||||
import ReactTimeAgo from "react-time-ago";
|
||||
import JavascriptTimeAgo from "javascript-time-ago";
|
||||
import timeagoFi from "javascript-time-ago/locale/fi";
|
||||
|
||||
JavascriptTimeAgo.addLocale(timeagoFi);
|
||||
|
||||
export default class Feed extends React.Component {
|
||||
state = {
|
||||
messages: [],
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.updateFeed();
|
||||
}
|
||||
|
||||
async updateFeed() {
|
||||
const res = await request.get(api("feeds"));
|
||||
const messages = sortBy(
|
||||
flatMap(res.data, (messages, type) => transformers[type](messages)),
|
||||
"timestamp"
|
||||
);
|
||||
messages.reverse(); // In-place
|
||||
this.setState({
|
||||
messages: messages.slice(0, 40),
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const messages = this.state.messages.map((message, i) => {
|
||||
let image = <img src={message.image} alt="" loading="lazy" />;
|
||||
|
||||
if (message.imageLink) {
|
||||
image = (
|
||||
<a
|
||||
target="_blank"
|
||||
href={message.imageLink}
|
||||
rel="noopener noreferrer"
|
||||
tabIndex={-1}
|
||||
>
|
||||
{image}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="message" key={i}>
|
||||
<div className="message__image" aria-hidden="true">
|
||||
{image}
|
||||
</div>
|
||||
<div className="message__content">
|
||||
<div className="message__user">
|
||||
<a href={message.userLink}>{message.user}</a>
|
||||
</div>
|
||||
<div
|
||||
className="message__body"
|
||||
dangerouslySetInnerHTML={{ __html: message.body }}
|
||||
/>
|
||||
<div className="message__icon">
|
||||
<i className={`fa fa-${message.type}`} aria-hidden="true" />
|
||||
</div>
|
||||
<div className="message__details">
|
||||
<span className="message__timestamp">
|
||||
<ReactTimeAgo date={message.timestamp} locale="fi" />
|
||||
</span>
|
||||
<span className="message__meta">{message.meta}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
return <div className="feed">{messages}</div>;
|
||||
}
|
||||
}
|
||||
@@ -1,76 +1,49 @@
|
||||
import React from "react";
|
||||
import EmailComponent from "./EmailComponent";
|
||||
import sponsors from "../data/sponsors";
|
||||
import Image from 'next/image';
|
||||
|
||||
type Props = {
|
||||
href: string;
|
||||
name: string;
|
||||
title?: string;
|
||||
};
|
||||
|
||||
const SponsorLink = ({ href, name }: Props) => (
|
||||
<a href={href} target="_blank" rel="noopener noreferrer">
|
||||
<img
|
||||
src={`/static/images/sponsors/${name.toLowerCase()}.svg`}
|
||||
alt={name}
|
||||
className={`sponsor sponsor__${name.toLowerCase()}`}
|
||||
loading="lazy"
|
||||
/>
|
||||
</a>
|
||||
);
|
||||
|
||||
const SocialLink = ({ href, name, title }: Props) => (
|
||||
<a href={href} target="_blank" rel="noopener noreferrer" aria-label={title}>
|
||||
<img
|
||||
src={`/static/images/social/${name.toLowerCase()}.svg`}
|
||||
alt={title}
|
||||
className={`social social__${name.toLowerCase()}`}
|
||||
loading="lazy"
|
||||
/>
|
||||
</a>
|
||||
);
|
||||
|
||||
export function Footer() {
|
||||
export default function Footer() {
|
||||
return (
|
||||
<footer>
|
||||
<div className="sponsors">
|
||||
<div className="sponsors__label">Yhteistyössä</div>
|
||||
{sponsors.map((sponsor) => (
|
||||
<SponsorLink key={sponsor.name} {...sponsor} />
|
||||
))}
|
||||
<div className="space-y-10 pt-24 text-center">
|
||||
<div className="flex items-center justify-center gap-10 ">
|
||||
<a
|
||||
href="https://koodiklinikka.slack.com/"
|
||||
className="opacity-50 transition-opacity hover:opacity-100 focus-visible:opacity-100"
|
||||
>
|
||||
<Image className="size-8" width="800" height="800" src="/logos/slack.svg" alt="Koodiklinikka Slack" />
|
||||
</a>
|
||||
<a
|
||||
href="https://github.com/koodiklinikka"
|
||||
className="opacity-50 transition-opacity hover:opacity-100 focus-visible:opacity-100"
|
||||
>
|
||||
<Image className="size-8" width="98" height="96" src="/logos/github.svg" alt="Koodiklinikka GitHub" />
|
||||
</a>
|
||||
<a
|
||||
href="https://x.com/koodiklinikka"
|
||||
className="opacity-50 transition-opacity hover:opacity-100 focus-visible:opacity-100"
|
||||
>
|
||||
<Image className="size-8" width="300" height="300" src="/logos/x.svg" alt="Koodiklinikka X" />
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://www.facebook.com/koodiklinikka"
|
||||
className="opacity-50 transition-opacity hover:opacity-100 focus-visible:opacity-100"
|
||||
>
|
||||
<Image className="size-8" width="40" height="40" src="/logos/facebook.svg" alt="Koodiklinikka Facebook" />
|
||||
</a>
|
||||
<a
|
||||
href="https://www.linkedin.com/groups/12025476"
|
||||
className="opacity-50 transition-opacity hover:opacity-100 focus-visible:opacity-100"
|
||||
>
|
||||
<Image className="size-8" width="531" height="530" src="/logos/linkedin.svg" alt="Koodiklinikka LinkedIn" />
|
||||
</a>
|
||||
</div>
|
||||
<div className="contacts">
|
||||
<div>
|
||||
<SocialLink
|
||||
href="https://koodiklinikka.slack.com"
|
||||
title="Koodiklinikka Slackissä"
|
||||
name="slack"
|
||||
/>
|
||||
<SocialLink
|
||||
href="https://github.com/koodiklinikka"
|
||||
title="Koodiklinikka Githubissa"
|
||||
name="github"
|
||||
/>
|
||||
<SocialLink
|
||||
href="https://twitter.com/koodiklinikka"
|
||||
title="Koodiklinikka Twitterissä"
|
||||
name="twitter"
|
||||
/>
|
||||
<SocialLink
|
||||
href="https://www.linkedin.com/groups/12025476"
|
||||
title="Koodiklinikka Linkedinissä"
|
||||
name="linkedin"
|
||||
/>
|
||||
<SocialLink
|
||||
href="https://www.facebook.com/koodiklinikka"
|
||||
title="Koodiklinikka Facebookissa"
|
||||
name="facebook"
|
||||
/>
|
||||
<div id="email">
|
||||
<EmailComponent />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<a
|
||||
href="mailto:info@koodiklinikka.fi"
|
||||
className="font-mono text-xs opacity-50 transition-opacity hover:opacity-100 focus-visible:opacity-100"
|
||||
>
|
||||
info@koodiklinikka.fi
|
||||
</a>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
138
components/Form.tsx
Normal file
@@ -0,0 +1,138 @@
|
||||
'use client';
|
||||
|
||||
import { FormEvent, ReactNode, useState } from 'react';
|
||||
|
||||
const API_URL = 'https://koodiklinikka-api.fly.dev/invites';
|
||||
|
||||
export default function Form() {
|
||||
const [message, setMessage] = useState<ReactNode | null>(null);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
async function handleSubmit(event: FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault();
|
||||
if (isSubmitting) return;
|
||||
|
||||
setIsSubmitting(true);
|
||||
|
||||
const formData = new FormData(event.currentTarget);
|
||||
const response = await fetch(API_URL, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(Object.fromEntries(formData)),
|
||||
});
|
||||
|
||||
const data = await response.text();
|
||||
|
||||
setIsSubmitting(false);
|
||||
|
||||
if (response.status === 200) {
|
||||
setMessage('✅ Kutsu lähetetty antamaasi sähköpostiosoitteeseen.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.status === 400 && data === 'invalid_email') {
|
||||
setMessage('⚠️ Tarkasta syöttämäsi sähköpostiosoite');
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.status === 400 && data === 'already_invited') {
|
||||
setMessage('♻️ Sähköpostiosoitteeseen on jo lähetetty kutsu');
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.status === 400 && data === 'already_in_team') {
|
||||
setMessage(
|
||||
<span>
|
||||
🤔 Tällä sähköpostilla on jo luotu tunnus.{' '}
|
||||
<a href="https://koodiklinikka.slack.com/forgot" className="underline underline-offset-4">
|
||||
Nollaa unohtunut salasana
|
||||
</a>
|
||||
.
|
||||
</span>
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
setMessage('⚡ Jotain meni pieleen. Yritä hetken päästä uudelleen.');
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-sm text-center md:max-w-xl">
|
||||
{message === null && (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<h2 className="font-mono text-sm font-semibold">
|
||||
Syötä sähköpostiosoitteesi alle ja saat kutsun Slack-yhteisöömme:
|
||||
</h2>
|
||||
|
||||
<div className="my-5 grid grid-cols-4 gap-2">
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
required
|
||||
className="col-span-3 grow rounded px-3 py-2 text-sm text-fuchsia-950 sm:text-base md:rounded-lg md:px-4 md:py-4 lg:rounded-lg lg:px-5 lg:py-5 lg:text-lg"
|
||||
placeholder="minna.meikalainen@example.org"
|
||||
tabIndex={1}
|
||||
/>
|
||||
<button
|
||||
tabIndex={3}
|
||||
type="submit"
|
||||
className="text-shadow bg-button rounded border border-pink-400 px-3 py-2 text-sm font-extrabold sm:text-base md:rounded-lg md:px-4 md:py-4 lg:px-5 lg:py-5 lg:text-lg"
|
||||
>
|
||||
{isSubmitting ? 'Liitytään' : 'Liity'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<label className="flex flex-wrap items-center justify-center gap-2 font-mono text-xxs sm:text-xs">
|
||||
<div className="relative h-5 w-5">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="terms"
|
||||
required
|
||||
className="h-3 w-3 opacity-5 focus:outline-none"
|
||||
tabIndex={2}
|
||||
/>
|
||||
<div className="checkbox absolute left-0 top-0 flex h-full w-full items-center justify-center rounded border border-white bg-transparent transition-colors">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" className="h-5 w-5">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M16.704 4.153a.75.75 0 0 1 .143 1.052l-8 10.5a.75.75 0 0 1-1.127.075l-4.5-4.5a.75.75 0 0 1 1.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 0 1 1.05-.143Z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
Sitoudun yhteisön
|
||||
<a
|
||||
className="inline-flex items-center gap-1 underline underline-offset-4"
|
||||
href="https://github.com/koodiklinikka/code-of-conduct/blob/master/README.md"
|
||||
target="_blank"
|
||||
>
|
||||
<span>käyttäytymissääntöihin</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" className="h-3 w-3">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M4.25 5.5a.75.75 0 0 0-.75.75v8.5c0 .414.336.75.75.75h8.5a.75.75 0 0 0 .75-.75v-4a.75.75 0 0 1 1.5 0v4A2.25 2.25 0 0 1 12.75 17h-8.5A2.25 2.25 0 0 1 2 14.75v-8.5A2.25 2.25 0 0 1 4.25 4h5a.75.75 0 0 1 0 1.5h-5Z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M6.194 12.753a.75.75 0 0 0 1.06.053L16.5 4.44v2.81a.75.75 0 0 0 1.5 0v-4.5a.75.75 0 0 0-.75-.75h-4.5a.75.75 0 0 0 0 1.5h2.553l-9.056 8.194a.75.75 0 0 0-.053 1.06Z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</label>
|
||||
</form>
|
||||
)}
|
||||
|
||||
{message && (
|
||||
<div className="text-balance rounded-3xl bg-black/20 p-10 text-center font-mono text-sm backdrop-blur-sm">
|
||||
{message}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
14
components/Hero.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import Form from './Form';
|
||||
|
||||
export default function Hero() {
|
||||
return (
|
||||
<div className="text-shadow mx-auto flex flex-col items-center justify-center" id="liity">
|
||||
<h1 className="my-14 text-center text-2xl font-extrabold leading-tight sm:max-w-[80%] sm:text-3xl md:my-24 md:text-4xl lg:my-32 lg:text-5xl">
|
||||
Koodiklinikka on Suomen suurin <span className="title-highlight">ohjelmistoalan yhteisö</span>, joka tuo alan
|
||||
ammattilaiset ja harrastajat yhteen
|
||||
</h1>
|
||||
|
||||
<Form />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
import request from "axios";
|
||||
import React from "react";
|
||||
import classSet from "classnames";
|
||||
import api from "./api";
|
||||
import Loader from "./Loader";
|
||||
|
||||
export default class InviteForm extends React.Component {
|
||||
state = {
|
||||
email: "",
|
||||
submitted: false,
|
||||
sending: false,
|
||||
error: null,
|
||||
};
|
||||
|
||||
onSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
this.setState({
|
||||
submitted: false,
|
||||
sending: true,
|
||||
error: null,
|
||||
});
|
||||
|
||||
try {
|
||||
await request.post(api("invites"), {
|
||||
email: this.state.email.trim(),
|
||||
});
|
||||
this.handleSuccess();
|
||||
} catch (error) {
|
||||
this.handleError(error);
|
||||
}
|
||||
};
|
||||
|
||||
handleSuccess = () => {
|
||||
this.setState({ submitted: true, sending: false });
|
||||
};
|
||||
|
||||
handleError = (err) => {
|
||||
this.setState({ error: err, sending: false });
|
||||
};
|
||||
|
||||
onChange = (e) => {
|
||||
if (e.target.value === this.state.email) {
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
email: e.target.value,
|
||||
error: null,
|
||||
submitted: false,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const formClasses = classSet({
|
||||
form: true,
|
||||
"invite-form": true,
|
||||
"has-success": this.state.submitted,
|
||||
"has-error": this.state.error,
|
||||
sending: this.state.sending,
|
||||
});
|
||||
|
||||
const inputClasses = classSet({
|
||||
input: true,
|
||||
"invite-form__input": true,
|
||||
"has-success": this.state.submitted,
|
||||
"has-error": this.state.error,
|
||||
});
|
||||
|
||||
let feedbackMessage;
|
||||
|
||||
if (this.state.error || this.state.submitted) {
|
||||
let messageText;
|
||||
|
||||
if (this.state.submitted) {
|
||||
messageText = "Kutsu lähetetty antamaasi sähköpostiosoitteeseen.";
|
||||
} else if (
|
||||
this.state.error.response.status === 400 &&
|
||||
this.state.error.response.data === "invalid_email"
|
||||
) {
|
||||
messageText = "Tarkasta syöttämäsi sähköpostiosoite";
|
||||
} else if (
|
||||
this.state.error.response.status === 400 &&
|
||||
this.state.error.response.data === "already_invited"
|
||||
) {
|
||||
messageText = "Sähköpostiosoitteeseen on jo lähetetty kutsu";
|
||||
} else if (
|
||||
this.state.error.response.status === 400 &&
|
||||
this.state.error.response.data === "already_in_team"
|
||||
) {
|
||||
messageText = (
|
||||
<span>
|
||||
Tällä sähköpostilla on jo luotu tunnus. <br /> Voit vaihtaa
|
||||
unohtuneen salasanasi{" "}
|
||||
<a href="https://koodiklinikka.slack.com/forgot">täältä</a>.
|
||||
</span>
|
||||
);
|
||||
} else {
|
||||
messageText = "Jotain meni pieleen. Yritä hetken päästä uudelleen.";
|
||||
}
|
||||
|
||||
feedbackMessage = <div className="form--message">{messageText}</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<form className={formClasses} onSubmit={this.onSubmit}>
|
||||
<div className="form__field">
|
||||
<label className="label" htmlFor="email-field">
|
||||
Sähköpostiosoite:
|
||||
</label>
|
||||
<div className="controls-wrapper">
|
||||
<span className="input-wrapper">
|
||||
<input
|
||||
className={inputClasses}
|
||||
type="text"
|
||||
name="email"
|
||||
id="email-field"
|
||||
// Placeholder is not accessible way to provide information
|
||||
// Used here for :placeholder-shown -styles
|
||||
placeholder=""
|
||||
value={this.state.email}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
<div className="invite-form__loader">
|
||||
<Loader />
|
||||
</div>
|
||||
</span>
|
||||
<button
|
||||
className="btn btn__submit"
|
||||
type="submit"
|
||||
title="Lähetä"
|
||||
disabled={this.state.error || this.state.submitted}
|
||||
>
|
||||
Lähetä
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{feedbackMessage}
|
||||
</form>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
export default function Loader() {
|
||||
return (
|
||||
<div className="sk-folding-cube">
|
||||
<div className="sk-cube1 sk-cube" />
|
||||
<div className="sk-cube2 sk-cube" />
|
||||
<div className="sk-cube4 sk-cube" />
|
||||
<div className="sk-cube3 sk-cube" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
import React from "react";
|
||||
import request from "axios";
|
||||
import shuffle from "lodash/shuffle";
|
||||
import api from "./api";
|
||||
|
||||
export default class Members extends React.Component {
|
||||
state = {
|
||||
members: [],
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.refreshMembers();
|
||||
}
|
||||
|
||||
async refreshMembers() {
|
||||
const res = await request.get(api("members"));
|
||||
this.setState({
|
||||
members: shuffle(res.data),
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const members = this.state.members.map((member) => {
|
||||
const src = `${member.avatar_url}&s=120`;
|
||||
return (
|
||||
<img
|
||||
className="member"
|
||||
key={member.avatar_url}
|
||||
src={src}
|
||||
alt=""
|
||||
width={30}
|
||||
height={30}
|
||||
loading="lazy"
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="members" aria-hidden="true">
|
||||
<a
|
||||
href="https://github.com/koodiklinikka"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
tabIndex={-1}
|
||||
>
|
||||
{members}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
82
components/Nav.tsx
Normal file
@@ -0,0 +1,82 @@
|
||||
'use client';
|
||||
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
import Wrapper from './Wrapper';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
export default function Nav() {
|
||||
const [navOpen, setNavOpen] = useState(false);
|
||||
const navRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (navRef.current && !navRef.current.contains(event.target as Node)) {
|
||||
setNavOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener('click', handleClickOutside, true);
|
||||
return () => {
|
||||
document.removeEventListener('click', handleClickOutside, true);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<nav className="fixed left-0 top-0 z-50 h-32 w-full bg-gradient-to-b from-black/40 to-fuchsia-950/0">
|
||||
<Wrapper>
|
||||
<div className="relative flex items-center justify-between px-6 py-5 md:px-12">
|
||||
<div className="shrink-0">
|
||||
<Image src="/koodiklinikka.svg" alt="Koodiklinikka" width="179" height="34" className="w-40" priority />
|
||||
</div>
|
||||
<div ref={navRef}>
|
||||
<button
|
||||
type="button"
|
||||
className="-mr-2 rounded bg-black/0 p-2 hover:bg-black/20 lg:hidden"
|
||||
onMouseDown={() => setNavOpen(!navOpen)}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth="1.5"
|
||||
stroke="currentColor"
|
||||
className="size-6"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
|
||||
</svg>
|
||||
</button>
|
||||
<div
|
||||
className={`${navOpen ? 'flex' : 'hidden'} text-shadow absolute right-6 top-16 flex-col gap-5 rounded-lg bg-black/80 p-5 text-xs uppercase tracking-widest text-pink-100 backdrop-blur-md lg:static lg:flex lg:flex-row lg:bg-transparent lg:backdrop-blur-none`}
|
||||
>
|
||||
<Link className="underline-offset-4 hover:underline" href="https://github.com/koodiklinikka">
|
||||
GitHub
|
||||
</Link>
|
||||
<NavSeparator />
|
||||
<Link className="underline-offset-4 hover:underline" href="https://koodiklinikka.slack.com">
|
||||
Slack
|
||||
</Link>
|
||||
<NavSeparator />
|
||||
<Link className="underline-offset-4 hover:underline" href="https://resources.koodiklinikka.fi">
|
||||
Resources
|
||||
</Link>
|
||||
<NavSeparator />
|
||||
<Link className="underline-offset-4 hover:underline" href="https://koodiklinikka.myspreadshop.fi/">
|
||||
Shop
|
||||
</Link>
|
||||
<NavSeparator />
|
||||
<Link
|
||||
className="underline-offset-4 hover:underline"
|
||||
href="https://github.com/koodiklinikka/code-of-conduct"
|
||||
>
|
||||
Code of Conduct
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Wrapper>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
||||
const NavSeparator = () => <div className="hidden opacity-20 lg:block">|</div>;
|
||||
5
components/Wrapper.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
export default function Wrapper({ children }: { children: ReactNode }) {
|
||||
return <div className="mx-auto max-w-6xl px-5">{children}</div>;
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
const host = process.env.SERVER || "https://koodiklinikka-api.fly.dev/";
|
||||
|
||||
export default function getApiURL(path) {
|
||||
return host + path;
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
import lodashTemplate from "lodash/template";
|
||||
import defaultTemplateSettings from "lodash/templateSettings";
|
||||
import githubEvent from "parse-github-event";
|
||||
import twitterText from "twitter-text";
|
||||
|
||||
const isVisibleGithubEvent = ({ type }) =>
|
||||
type !== "PushEvent" && type !== "DeleteEvent";
|
||||
|
||||
const templateSettings = {
|
||||
...defaultTemplateSettings,
|
||||
interpolate: /{{([\s\S]+?)}}/g,
|
||||
};
|
||||
|
||||
export default {
|
||||
github(items) {
|
||||
return items.filter(isVisibleGithubEvent).map((item) => {
|
||||
const template = lodashTemplate(
|
||||
githubEvent.parse(item).text,
|
||||
templateSettings,
|
||||
false
|
||||
);
|
||||
|
||||
const repository = `https://github.com/${item.repo.name}`;
|
||||
let branch;
|
||||
if (item.payload.ref) {
|
||||
branch = item.payload.ref.replace("refs/heads/", "");
|
||||
}
|
||||
|
||||
const message = template({
|
||||
repository: `<a target="_blank" href="${repository}">${item.repo.name}</a>`,
|
||||
branch: branch,
|
||||
number: item.payload.number,
|
||||
ref_type: item.payload.ref_type,
|
||||
ref: item.payload.ref,
|
||||
});
|
||||
|
||||
const url = `https://github.com/${item.actor.login}`;
|
||||
|
||||
return {
|
||||
user: item.actor.login,
|
||||
userLink: url,
|
||||
image: item.actor.avatar_url,
|
||||
imageLink: url,
|
||||
body: message,
|
||||
timestamp: new Date(item.created_at),
|
||||
url: message.url,
|
||||
type: "github",
|
||||
};
|
||||
});
|
||||
},
|
||||
twitter(items) {
|
||||
return items.map((item) => {
|
||||
if (item.retweeted) {
|
||||
item = item.retweeted_status;
|
||||
}
|
||||
|
||||
const url = `https://twitter.com/${item.user.screen_name}`;
|
||||
|
||||
return {
|
||||
user: `@${item.user.screen_name}`,
|
||||
userLink: url,
|
||||
image: item.user.profile_image_url_https,
|
||||
imageLink: url,
|
||||
body: twitterText.autoLink(item.text),
|
||||
timestamp: new Date(item.created_at),
|
||||
type: "twitter",
|
||||
};
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -1,39 +0,0 @@
|
||||
import React from "react";
|
||||
import MembershipInfoForm from "./MembershipInfoForm";
|
||||
|
||||
export default class MembershipForm extends React.Component {
|
||||
state = {
|
||||
signupSuccess: false,
|
||||
};
|
||||
|
||||
handleSignupSuccess = () => {
|
||||
this.setState({ signupSuccess: true });
|
||||
};
|
||||
|
||||
render() {
|
||||
if (!this.state.signupSuccess) {
|
||||
return <MembershipInfoForm onSignupSuccess={this.handleSignupSuccess} />;
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<svg
|
||||
height="50"
|
||||
width="50"
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill="#349c4a"
|
||||
d="M256 6.998c-137.533 0-249 111.467-249 249 0 137.534 111.467 249 249 249s249-111.467 249-249c0-137.534-111.467-249-249-249zm0 478.08c-126.31 0-229.08-102.77-229.08-229.08 0-126.31 102.77-229.08 229.08-229.08 126.31 0 229.08 102.77 229.08 229.08 0 126.31-102.77 229.08-229.08 229.08z"
|
||||
/>
|
||||
<path
|
||||
fill="#349c4a"
|
||||
d="M384.235 158.192L216.92 325.518 127.86 236.48l-14.142 14.144 103.2 103.18 181.36-181.47"
|
||||
/>
|
||||
</svg>
|
||||
<p> Rekisteröityminen onnistui. Tervetuloa jäseneksi!</p>
|
||||
<p> Tervetuloa Koodiklinikka ry:n jäseneksi!</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,212 +0,0 @@
|
||||
import request from "axios";
|
||||
import React from "react";
|
||||
import classSet from "classnames";
|
||||
import api from "../api";
|
||||
import Loader from "../Loader";
|
||||
|
||||
type Props = {
|
||||
onSignupSuccess: () => void;
|
||||
};
|
||||
|
||||
type State = {
|
||||
error: boolean;
|
||||
errors: string[];
|
||||
fields: {
|
||||
name: string;
|
||||
email: string;
|
||||
handle: string;
|
||||
address: string;
|
||||
postcode: string;
|
||||
city: string;
|
||||
};
|
||||
sending: boolean;
|
||||
pristineFields: string[];
|
||||
};
|
||||
|
||||
const 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" },
|
||||
};
|
||||
|
||||
const mailValidateRe =
|
||||
/^(([^<>()[\]\\.,;:\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,}))$/;
|
||||
|
||||
function validateEmail(email) {
|
||||
return mailValidateRe.test(email);
|
||||
}
|
||||
|
||||
function getUserInfo(state) {
|
||||
return state.fields;
|
||||
}
|
||||
|
||||
export default class MembershipInfoForm extends React.Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.setState({
|
||||
fields: {
|
||||
address: "",
|
||||
city: "",
|
||||
email: "",
|
||||
handle: "",
|
||||
name: "",
|
||||
postcode: "",
|
||||
},
|
||||
sending: false,
|
||||
pristineFields: Object.keys(this.state.fields),
|
||||
});
|
||||
}
|
||||
|
||||
onSubmit = async () => {
|
||||
this.setState({
|
||||
sending: true,
|
||||
error: null,
|
||||
});
|
||||
|
||||
try {
|
||||
await request.post(api("membership"), {
|
||||
userInfo: getUserInfo(this.state),
|
||||
});
|
||||
this.setState({ sending: false });
|
||||
this.props.onSignupSuccess();
|
||||
} catch (err) {
|
||||
this.setState({ error: err, sending: false });
|
||||
}
|
||||
};
|
||||
|
||||
onChange = (e) => {
|
||||
const name = e.target.name;
|
||||
if (e.target.value === this.state[name]) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
fields: {
|
||||
...this.state.fields,
|
||||
[name]: e.target.value,
|
||||
},
|
||||
pristineFields: this.state.pristineFields.filter(
|
||||
(fieldName) => fieldName !== name
|
||||
),
|
||||
errors: [],
|
||||
});
|
||||
};
|
||||
|
||||
getDataErrors = () => {
|
||||
const foundErrors = [];
|
||||
|
||||
Object.keys(this.state.fields).forEach((fieldName) => {
|
||||
if (!this.state[fieldName]) {
|
||||
foundErrors.push({ field: fieldName, type: "missing" });
|
||||
}
|
||||
});
|
||||
|
||||
if (this.state.fields.email && !validateEmail(this.state.fields.email)) {
|
||||
foundErrors.push({ field: "email", type: "invalid" });
|
||||
}
|
||||
|
||||
return foundErrors;
|
||||
};
|
||||
|
||||
render() {
|
||||
const inputErrors = this.getDataErrors();
|
||||
|
||||
const formClasses = classSet({
|
||||
form: true,
|
||||
"membership-form": true,
|
||||
"has-error": inputErrors.length !== 0 || this.state.error,
|
||||
sending: this.state.sending,
|
||||
});
|
||||
|
||||
function getErrorMessage(err) {
|
||||
let feedbackText;
|
||||
|
||||
const fieldName = fieldNameTranslations[err.field].fi;
|
||||
if (err.type === "missing") {
|
||||
feedbackText = `${fieldName} on pakollinen.`;
|
||||
} else if (err.type === "invalid") {
|
||||
feedbackText = `${fieldName} on virheellinen.`;
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={err.field} className="form--message">
|
||||
{feedbackText}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/* generate error messages */
|
||||
const visibleErrors = inputErrors.filter(
|
||||
(error) => this.state.pristineFields.indexOf(error.field) === -1
|
||||
);
|
||||
|
||||
const fieldsWithErrors = visibleErrors.map(({ field }) => field);
|
||||
|
||||
const inputFields = Object.keys(this.state.fields).map((fieldName) => {
|
||||
const inputClasses = classSet({
|
||||
input: true,
|
||||
"has-error": fieldsWithErrors.includes(fieldName),
|
||||
half: fieldName === "city" || fieldName === "postcode",
|
||||
left: fieldName === "postcode",
|
||||
});
|
||||
|
||||
function showsErrorFor(field) {
|
||||
if (fieldName === "city") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
field === fieldName || (fieldName === "postcode" && field === "city")
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<span key={fieldName}>
|
||||
<input
|
||||
className={inputClasses}
|
||||
type={fieldName === "email" ? "email" : "text"}
|
||||
name={fieldName}
|
||||
placeholder={fieldNameTranslations[fieldName].fi}
|
||||
value={this.state[fieldName]}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
{visibleErrors
|
||||
.filter(({ field }) => showsErrorFor(field))
|
||||
.map(getErrorMessage)}
|
||||
</span>
|
||||
);
|
||||
});
|
||||
if (this.state.sending) {
|
||||
return (
|
||||
<div className="membership-form__loader">
|
||||
<Loader />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<h3>Liity jäseneksi</h3>
|
||||
<form className={formClasses}>
|
||||
{inputFields}
|
||||
{this.state.error && (
|
||||
<div className="form--message">
|
||||
Jotain meni pieleen! Ota yhteyttä info@koodiklinikka.fi
|
||||
</div>
|
||||
)}
|
||||
<br />
|
||||
<button
|
||||
type="button"
|
||||
disabled={inputErrors.length !== 0}
|
||||
className="btn btn__submit"
|
||||
onClick={this.onSubmit}
|
||||
>
|
||||
Liity jäseneksi
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import axios from "axios";
|
||||
|
||||
export interface Channel {
|
||||
id: string;
|
||||
name: string;
|
||||
topic: string;
|
||||
num_members: number;
|
||||
purpose: {
|
||||
value: string;
|
||||
creator: string;
|
||||
last_set: number;
|
||||
};
|
||||
messages_today: number;
|
||||
unique_members_today: number;
|
||||
}
|
||||
|
||||
export async function getChannels() {
|
||||
const response = await axios.get<Channel[]>(
|
||||
"http://stats.koodiklinikka.fi/api/channels"
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
const sponsors = [
|
||||
{
|
||||
name: "Futurice",
|
||||
href: "https://futurice.com/",
|
||||
},
|
||||
{
|
||||
name: "Metosin",
|
||||
href: "https://www.metosin.fi/",
|
||||
},
|
||||
{
|
||||
name: "Solita",
|
||||
href: "https://www.solita.fi/",
|
||||
},
|
||||
{
|
||||
name: "Wakeone",
|
||||
href: "https://wakeone.co/",
|
||||
},
|
||||
{
|
||||
name: "Nordea",
|
||||
href: "https://www.nordea.fi/",
|
||||
},
|
||||
{
|
||||
name: "Idean",
|
||||
href: "https://www.idean.com/",
|
||||
},
|
||||
{
|
||||
name: "Rare",
|
||||
href: "https://rare.fi/",
|
||||
},
|
||||
];
|
||||
export default sponsors;
|
||||
|
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 9.5 KiB |
9
next.config.mjs
Normal file
@@ -0,0 +1,9 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
output: 'export',
|
||||
images: {
|
||||
unoptimized: true,
|
||||
},
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
65
package.json
@@ -1,49 +1,42 @@
|
||||
{
|
||||
"name": "koodiklinikka",
|
||||
"version": "1.0.0",
|
||||
"description": "Koodiklinikka homepage",
|
||||
"author": "Riku Rouvila <riku@rare.fi>",
|
||||
"version": "2.0.0",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"start": "next",
|
||||
"build": "next build && next export",
|
||||
"dev": "SERVER=http://localhost:9000/ NODE_ENV=development next",
|
||||
"prod": "NODE_ENV=production next build && next export",
|
||||
"lint": "eslint --ext .tsx --ext .ts .",
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"prod": "NODE_ENV=production next build",
|
||||
"prettify": "prettier --write ."
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.27.2",
|
||||
"classnames": "^2.3.1",
|
||||
"javascript-time-ago": "^2.3.13",
|
||||
"less": "^4.1.2",
|
||||
"lodash": "^4.17.21",
|
||||
"next": "^12.1.6",
|
||||
"parse-github-event": "^1.1.3",
|
||||
"react": "^18.1.0",
|
||||
"react-dom": "^18.1.0",
|
||||
"react-ga": "^3.3.0",
|
||||
"react-markdown": "^8.0.3",
|
||||
"react-time-ago": "^7.1.9",
|
||||
"sass": "^1.51.0",
|
||||
"twitter-text": "^3.0.0",
|
||||
"utility-types": "^3.10.0"
|
||||
"lodash.shuffle": "^4.2.0",
|
||||
"next": "14.2.3",
|
||||
"react": "^18",
|
||||
"react-dom": "^18"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next/eslint-plugin-next": "^12.1.6",
|
||||
"@types/node": "17.0.31",
|
||||
"@types/react": "18.0.8",
|
||||
"@typescript-eslint/eslint-plugin": "^5.22.0",
|
||||
"@typescript-eslint/parser": "^5.22.0",
|
||||
"eslint": "^8.14.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.5.1",
|
||||
"eslint-plugin-react": "^7.29.4",
|
||||
"eslint-plugin-react-hooks": "^4.5.0",
|
||||
"prettier": "^2.6.2",
|
||||
"typescript": "4.6.4"
|
||||
"@types/lodash.shuffle": "^4.2.9",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "14.2.3",
|
||||
"postcss": "^8",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-plugin-tailwindcss": "^0.5.14",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5"
|
||||
},
|
||||
"prettier": {
|
||||
"trailingComma": "es5"
|
||||
"printWidth": 120,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5",
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"plugins": [
|
||||
"prettier-plugin-tailwindcss"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
import React from "react";
|
||||
import Head from "next/head";
|
||||
|
||||
import "../styles/style.scss";
|
||||
|
||||
const metaImage = "/static/images/meta.jpg";
|
||||
const metaDescription =
|
||||
"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.";
|
||||
const metaShortDescription =
|
||||
"Koodiklinikka on suomalainen yhteisö ohjelmistoalan harrastajille ja ammattilaisille.";
|
||||
const metaTitle = "Koodiklinikka – Suomen suurin ohjelmistoalan yhteisö";
|
||||
const metaKeywords =
|
||||
"ohjelmointi,frontend,open source,devaus,suomi,javascript,clojure,go,java,node.js,io.js,angular.js,web";
|
||||
const metaUrl = "https://koodiklinikka.fi/";
|
||||
|
||||
function MyApp({ Component, pageProps }) {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<meta name="title" content={metaTitle} />
|
||||
<meta name="description" content={metaShortDescription} />
|
||||
<meta name="keywords" content={metaKeywords} />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content={metaUrl} />
|
||||
<meta property="og:title" content={metaTitle} />
|
||||
<meta property="og:description" content={metaDescription} />
|
||||
<meta property="og:image" content={metaImage} />
|
||||
<meta property="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:url" content={metaUrl} />
|
||||
<meta property="twitter:title" content={metaTitle} />
|
||||
<meta property="twitter:description" content={metaDescription} />
|
||||
<meta property="twitter:image" content={metaImage} />
|
||||
<meta charSet="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
href="/static/icons/apple-touch-icon.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="32x32"
|
||||
href="/static/icons/favicon-32x32.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="16x16"
|
||||
href="/static/icons/favicon-16x16.png"
|
||||
/>
|
||||
<link rel="manifest" href="/static/icons/site.webmanifest" />
|
||||
</Head>
|
||||
<Component {...pageProps} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default MyApp;
|
||||
@@ -1,48 +0,0 @@
|
||||
import React from "react";
|
||||
import Document, { Html, Head, Main, NextScript } from "next/document";
|
||||
import { Footer } from "../components/Footer";
|
||||
import Fader from "../components/Fader";
|
||||
import ReactGA from "react-ga";
|
||||
|
||||
function trackPageView() {
|
||||
if (location.hostname === "localhost" || location.hostname === "127.0.0.1") {
|
||||
return;
|
||||
}
|
||||
if (!window.GA_INITIALIZED) {
|
||||
ReactGA.initialize("UA-58806132-1");
|
||||
window.GA_INITIALIZED = true;
|
||||
}
|
||||
ReactGA.set({ page: window.location.pathname });
|
||||
ReactGA.pageview(window.location.pathname);
|
||||
}
|
||||
|
||||
class MyDocument extends Document {
|
||||
static async getInitialProps(ctx) {
|
||||
const initialProps = await Document.getInitialProps(ctx);
|
||||
return { ...initialProps };
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
trackPageView();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Html lang="fi">
|
||||
<Head />
|
||||
<body>
|
||||
<div className="site">
|
||||
<div className="container">
|
||||
<Main />
|
||||
</div>
|
||||
<Footer />
|
||||
</div>
|
||||
<Fader />
|
||||
<NextScript />
|
||||
</body>
|
||||
</Html>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default MyDocument;
|
||||
196
pages/index.tsx
@@ -1,196 +0,0 @@
|
||||
import React from "react";
|
||||
import Head from "next/head";
|
||||
import { PromiseType } from "utility-types";
|
||||
import InviteForm from "../components/InviteForm";
|
||||
import Members from "../components/Members";
|
||||
import Feed from "../components/Feed";
|
||||
import { getChannels } from "../data/channels";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import { ChannelReferenceRenderer } from "../components/ChannelReferenceRenderer";
|
||||
|
||||
export async function getStaticProps() {
|
||||
const allChannels = await getChannels();
|
||||
const channels = allChannels
|
||||
.sort((a, b) => b.num_members - a.num_members)
|
||||
.sort((a, b) => b.unique_members_today - a.unique_members_today);
|
||||
return {
|
||||
props: {
|
||||
channels: channels,
|
||||
},
|
||||
revalidate: 3600,
|
||||
};
|
||||
}
|
||||
|
||||
type IndexProps = PromiseType<ReturnType<typeof getStaticProps>>["props"];
|
||||
|
||||
const Hero = () => (
|
||||
<div className="header">
|
||||
<video
|
||||
autoPlay
|
||||
loop
|
||||
muted
|
||||
poster="/static/images/poster.jpg"
|
||||
className="header__video-bg"
|
||||
>
|
||||
<source src="/static/videos/jumbo.mp4" type="video/mp4" />
|
||||
</video>
|
||||
<div className="header__container">
|
||||
<div className="header__nav">
|
||||
<a href="/">
|
||||
<img src="/static/images/logo-new.svg" alt="Etusivu" />
|
||||
</a>
|
||||
</div>
|
||||
<div className="header__headline">
|
||||
<h1 className="header__title">
|
||||
Yhteisö kaikille ohjelmoinnista ja ohjelmistoalasta kiinnostuneille
|
||||
harrastajille ja ammattilaisille.
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const IndexContent = (props: IndexProps) => (
|
||||
<>
|
||||
<div className="content with-feed">
|
||||
<section>
|
||||
<div className="row">
|
||||
<h3>
|
||||
Tule mukaan{" "}
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://slack.com/"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Slack
|
||||
</a>
|
||||
-yhteisöömme
|
||||
</h3>
|
||||
<div className="form">
|
||||
<InviteForm />
|
||||
</div>
|
||||
<p className="code-of-conduct">
|
||||
Ennen liittymistä yhteisöömme varmista, että olet lukenut yhteisön{" "}
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://github.com/koodiklinikka/code-of-conduct/blob/master/README.md"
|
||||
>
|
||||
käyttäytymissäännöt
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<div className="row">
|
||||
<div className="bread">
|
||||
<div className="column column1-2">
|
||||
<h3>Yhteisö ohjelmoinnista kiinnostuneille</h3>
|
||||
<p>
|
||||
Koodiklinikka on Suomen suurin ohjelmistoalan yhteisö, joka
|
||||
kokoaa työntekijät, harrastajat ja vasta-alkajat yhteen.{"\n"}
|
||||
Tarkoituksenamme on yhdistää ja kasvattaa suomalaista
|
||||
ohjelmointiyhteisöä, sekä tarjota apua ja uusia kontakteja
|
||||
ohjelmoinnista innostuneille nuorille.
|
||||
</p>
|
||||
<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.
|
||||
</p>
|
||||
</div>
|
||||
<div className="column column1-2">
|
||||
<a href="/static/images/slack.png" target="_blank">
|
||||
<img
|
||||
src="/static/images/slack.png"
|
||||
alt="Slack app at Koodiklinikka"
|
||||
loading="lazy"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="bread">
|
||||
<div className="column column5-5">
|
||||
<h3>Suosituimmat keskustelunaiheet tänään</h3>
|
||||
|
||||
<table className="channels">
|
||||
<tbody>
|
||||
{props.channels.slice(0, 10).map((channel) => (
|
||||
<tr key={channel.id}>
|
||||
<td>
|
||||
<div>
|
||||
<a
|
||||
href={`https://app.slack.com/client/T03BQ3NU9/${channel.id}`}
|
||||
target="_blank"
|
||||
className="channel"
|
||||
>
|
||||
#{channel.name}
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span className="channel-members">
|
||||
{channel.num_members} jäsentä
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
<p>
|
||||
<strong>Ja paljon muuta:</strong>{" "}
|
||||
{props.channels.slice(10, 30).map((channel, i) => (
|
||||
<React.Fragment key={channel.id}>
|
||||
<a
|
||||
href={`https://app.slack.com/client/T03BQ3NU9/${channel.id}`}
|
||||
target="_blank"
|
||||
>
|
||||
#{channel.name}
|
||||
</a>
|
||||
{i !== 19 ? ", " : "..."}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="bread">
|
||||
<h3>Avoin lähdekoodi</h3>
|
||||
<p>
|
||||
Suosimme avointa lähdekoodia ja kaikki käyttämämme koodi on
|
||||
vapaasti saatavilla ja hyödynnettävissä{" "}
|
||||
<a href="https://github.com/koodiklinikka">
|
||||
Github-organisaatiomme sivulta
|
||||
</a>
|
||||
. Organisaation jäseneksi otamme kaikki Slack-yhteisömme jäsenet.
|
||||
Koodiklinikan projekteihin voi osallistua kuka tahansa ja
|
||||
muutosideat ovat aina lämpimästi tervetulleita.
|
||||
</p>
|
||||
<div id="members">
|
||||
<Members />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<div id="feed">
|
||||
<Feed />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
const Index = (props: IndexProps) => (
|
||||
<React.Fragment>
|
||||
<Head>
|
||||
<title>Koodiklinikka</title>
|
||||
</Head>
|
||||
<Hero />
|
||||
<IndexContent {...props} />
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
export default Index;
|
||||
8
postcss.config.mjs
Normal file
@@ -0,0 +1,8 @@
|
||||
/** @type {import('postcss-load-config').Config} */
|
||||
const config = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
BIN
public/background.webp
Normal file
|
After Width: | Height: | Size: 499 KiB |
1
public/koodiklinikka.svg
Normal file
|
After Width: | Height: | Size: 10 KiB |
1
public/logos/facebook.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg fill="none" height="40" viewBox="0 0 40 40" width="40" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><clipPath id="a"><path d="m0 0h40v40h-40z"/></clipPath><g clip-path="url(#a)"><path clip-rule="evenodd" d="m0 20c0 9.9 7.2 18.1 16.7 19.8l.1182-.0967c-.0061-.0011-.0121-.0022-.0182-.0033v-14.1h-5v-5.6h5v-4.4c0-5 3.2-7.80001 7.8-7.80001 1.4 0 3 .2 4.4.4v5.10001h-2.6c-2.4 0-3 1.2-3 2.8v3.9h5.3l-.9 5.6h-4.4v14.1c-.0609.0111-.1218.0218-.1828.0323l.0828.0677c9.5-1.7 16.7-9.9 16.7-19.8 0-11-9-20-20-20s-20 9-20 20z" fill="#fff" fill-rule="evenodd"/></g></svg>
|
||||
|
After Width: | Height: | Size: 594 B |
4
public/logos/github.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="98" height="96"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z" fill="#fff"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 967 B |
1
public/logos/linkedin.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg fill="none" height="530" viewBox="0 0 531 530" width="531" xmlns="http://www.w3.org/2000/svg"><path d="m452.082 451.576h-78.471v-122.888c0-29.304-.523-67.027-40.813-67.027-40.87 0-47.123 31.928-47.123 64.894v125.013h-78.471v-252.708h75.332v34.535h1.055c7.538-12.89 18.432-23.494 31.521-30.683 13.089-7.188 27.882-10.692 42.805-10.138 79.534 0 94.198 52.314 94.198 120.371zm-333.419-287.26c-9.007.002-17.811-2.667-25.3008-7.67-7.4895-5.002-13.3275-12.113-16.7756-20.433-3.4481-8.321-4.3516-17.477-2.5961-26.31 1.7555-8.834 6.0911-16.9489 12.4585-23.3186s14.481-10.7082 23.314-12.4668c8.833-1.7587 17.989-.8586 26.311 2.5866 8.321 3.4451 15.434 9.2805 20.439 16.7682 5.005 7.4876 7.678 16.2916 7.679 25.2976.001 5.98-1.175 11.902-3.463 17.427-2.287 5.526-5.641 10.546-9.869 14.776-4.227 4.229-9.247 7.584-14.771 9.874-5.525 2.289-11.446 3.468-17.426 3.469zm39.235 287.26h-78.5523v-252.716h78.5523zm333.305-451.206619h-451.2888c-10.243-.115592-20.1129 3.840119-27.4408 10.997919-7.32789 7.1578-11.514314 16.932-11.639324 27.1749v453.1608c.120731 10.248 4.304474 20.029 11.632124 27.194 7.3277 7.165 17.1999 11.129 27.448 11.02h451.2888c10.268.128 20.168-3.824 27.525-10.989 7.356-7.165 11.568-16.957 11.71-27.225v-453.1935c-.146-10.2632-4.361-20.0486-11.717-27.2062-7.357-7.15756-17.255-11.101974-27.518-10.966615" fill="#fff"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
1
public/logos/slack.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg fill="#fff" height="800" viewBox="0 0 512 512" width="800" xmlns="http://www.w3.org/2000/svg"><path d="m126.12 315.1a47.06 47.06 0 1 1 -47.06-47.1h47.06z"/><path d="m149.84 315.1a47.06 47.06 0 0 1 94.12 0v117.84a47.06 47.06 0 1 1 -94.12 0z"/><path d="m196.9 126.12a47.06 47.06 0 1 1 47.1-47.06v47.06z"/><path d="m196.9 149.84a47.06 47.06 0 0 1 0 94.12h-117.84a47.06 47.06 0 0 1 0-94.12z"/><path d="m385.88 196.9a47.06 47.06 0 1 1 47.06 47.1h-47.06z"/><path d="m362.16 196.9a47.06 47.06 0 0 1 -94.12 0v-117.84a47.06 47.06 0 1 1 94.12 0z"/><path d="m315.1 385.88a47.06 47.06 0 1 1 -47.1 47.06v-47.06z"/><path d="m315.1 362.16a47.06 47.06 0 0 1 0-94.12h117.84a47.06 47.06 0 1 1 0 94.12z"/></svg>
|
||||
|
After Width: | Height: | Size: 697 B |
4
public/logos/x.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="300" height="300.251" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M178.57 127.15 290.27 0h-26.46l-97.03 110.38L89.34 0H0l117.13 166.93L0 300.25h26.46l102.4-116.59 81.8 116.59h89.34M36.01 19.54H76.66l187.13 262.13h-40.66" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 274 B |
BIN
public/meetup.webp
Normal file
|
After Width: | Height: | Size: 91 KiB |
BIN
public/opensource.webp
Normal file
|
After Width: | Height: | Size: 87 KiB |
|
Before Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 182 B |
|
Before Width: | Height: | Size: 340 B |
|
Before Width: | Height: | Size: 15 KiB |
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"name": "",
|
||||
"short_name": "",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"theme_color": "#ffffff",
|
||||
"background_color": "#ffffff",
|
||||
"display": "standalone"
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<svg height="512" viewBox="0 0 512 512" width="512" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path d="m.5.5h511v511h-511z" fill="#f50" stroke="#979797"/><path d="m416 206.000625c0 60.75125-49.24875 109.999375-110 109.999375-7.0125 0-13.86875-.66375-20.516875-1.918125l-15.0075 16.88375c-2.846519 3.202358-6.926653 5.034529-11.21125 5.034375h-23.264375v25c0 8.284375-6.715625 15-15 15h-25v25c0 8.284375-6.715625 15-15 15h-70c-8.284375 0-15-6.715625-15-15v-48.786875c0-3.978125 1.580625-7.79375 4.393125-10.606875l101.12625-101.12625c-3.576875-10.84625-5.519375-22.435625-5.519375-34.48 0-60.75125 49.248125-109.999375 109.999375-110 60.930625-.000625 110.000625 49.069375 110.000625 110.000625zm-110-30.000625c0 16.56875 13.43125 30 30 30s30-13.43125 30-30-13.43125-30-30-30-30 13.43125-30 30z" fill="#fff" fill-rule="nonzero"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 866 B |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 268 KiB |
|
Before Width: | Height: | Size: 293 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 180 KiB |
|
Before Width: | Height: | Size: 4.9 KiB |
@@ -1 +0,0 @@
|
||||
<svg height="129" viewBox="0 0 143 129" width="143" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path d="m125 75 12.5 25h-25zm0-50 12.5 25h-25zm-25 50 12.5 25h-25zm12.5-25 12.5 25h-25zm-100 0 12.5 25h-25zm25-50 12.5 25h-25zm-25 0 12.5 25h-25zm12.5 25 12.5 25h-25zm25 50 12.5 25h-25zm12.5-25 12.5 25h-25zm12.5-25 12.5 25h-25zm12.5 25 12.5 25h-25z" fill="#e8dc64"/><path d="m25 75 12.5-25h-25zm25-50 12.5-25h-25zm-25 0 12.5-25h-25zm12.5 25 12.5-25h-25zm-25 0 12.5-25h-25zm50 50 12.5-25h-25zm37.5-25 12.5-25h-25zm12.5 25 12.5-25h-25zm12.5-25 12.5-25h-25zm0-50 12.5-25h-25zm-112.5 75 12.5-25h-25zm75-50 12.5-25h-25z" fill="#fdf06f"/><path d="m25 75 12.5 25h-25zm50 0 12.5 25h-25zm-37.5-25 12.5 25h-25zm12.5-25 12.5 25h-25zm37.5-25 12.5 25h-25zm25 0 12.5 25h-25zm-12.5 25 12.5 25h-25zm-37.5-25 12.5 25h-25zm25 50 12.5 25h-25z" fill="#ba152b"/><path d="m100 25 12.5-25h-25zm-62.5 75 12.5-25h-25zm12.5-25 12.5-25h-25zm25-50 12.5-25h-25zm12.5 75 12.5-25h-25zm25-50 12.5-25h-25zm-37.5 25 12.5-25h-25zm-12.5-25 12.5-25h-25z" fill="#d31b33"/><path d="m21.78 104.67c1.76 0 3.218.186 4.373.56 1.156.374 2.074.902 2.756 1.584.68.682 1.16 1.485 1.434 2.41.276.923.413 1.946.413 3.068 0 1.54-.324 2.915-.974 4.125s-1.765 2.123-3.35 2.74l4.82 8.843h-5.644l-4.324-8.085h-3.828v8.085h-5.214v-23.33h9.537zm-4.322 11.12h3.762c.858 0 1.562-.077 2.112-.23.55-.155.99-.38 1.32-.678.33-.297.56-.66.693-1.09.132-.428.198-.917.198-1.467 0-.528-.066-1.006-.198-1.436s-.363-.787-.693-1.073-.77-.506-1.32-.66-1.254-.23-2.112-.23h-3.762v6.863zm17.457 12.21v-23.33h16.565v4.29h-11.35v5.114h9.833v4.323h-9.833v5.28h11.35v4.323h-16.564zm26.532-2.41c0 .793-.225 1.453-.677 1.98-.45.53-1.193.793-2.227.793s-1.77-.258-2.21-.775c-.44-.518-.66-1.183-.66-1.997 0-.77.22-1.412.66-1.93.44-.517 1.176-.775 2.21-.775s1.776.258 2.227.775c.452.518.677 1.16.677 1.93zm0-11.714c0 .792-.225 1.452-.677 1.98-.45.528-1.193.792-2.227.792s-1.77-.253-2.21-.76c-.44-.505-.66-1.176-.66-2.012 0-.77.22-1.413.66-1.93.44-.518 1.176-.776 2.21-.776s1.776.258 2.227.775c.452.518.677 1.16.677 1.93zm4.52-9.207h8.482c2.22 0 4.07.3 5.543.906 1.474.606 2.65 1.436 3.53 2.492s1.508 2.293 1.882 3.713.56 2.954.56 4.604c0 1.606-.19 3.113-.577 4.52-.385 1.41-1.022 2.64-1.913 3.697-.892 1.057-2.074 1.887-3.548 2.493-1.474.605-3.3.907-5.478.907h-8.482v-23.33zm9.24 19.007c.99 0 1.838-.18 2.542-.545.703-.363 1.275-.874 1.715-1.534s.764-1.435.974-2.326c.208-.892.312-1.865.312-2.92 0-1.057-.1-2.036-.297-2.938-.198-.902-.517-1.683-.957-2.343s-1.017-1.176-1.733-1.55c-.715-.374-1.59-.56-2.623-.56h-3.96v14.717h4.026zm24.685 4.686c-1.804 0-3.388-.286-4.752-.858s-2.508-1.375-3.432-2.41c-.924-1.033-1.622-2.28-2.096-3.745-.473-1.463-.71-3.096-.71-4.9 0-1.848.243-3.52.727-5.016.483-1.496 1.198-2.772 2.144-3.828s2.117-1.87 3.514-2.442c1.398-.572 2.998-.858 4.802-.858s3.393.28 4.77.84c1.374.563 2.528 1.365 3.463 2.41.936 1.046 1.645 2.3 2.13 3.763.483 1.462.725 3.095.725 4.9 0 1.803-.242 3.453-.726 4.95-.484 1.495-1.204 2.776-2.162 3.843-.957 1.068-2.133 1.892-3.53 2.475-1.398.584-3.02.875-4.868.875zm.198-4.29c1.848 0 3.29-.65 4.323-1.947 1.034-1.298 1.55-3.245 1.55-5.84 0-2.553-.51-4.472-1.533-5.76-1.024-1.287-2.47-1.93-4.34-1.93-1.914 0-3.388.654-4.422 1.963-1.034 1.31-1.55 3.228-1.55 5.758 0 2.574.527 4.51 1.583 5.808 1.057 1.298 2.52 1.947 4.39 1.947zm21.846-19.404 5.445 13.265 5.446-13.266h5.907l1.815 23.33h-5.249l-1.055-13.464-.033-2.442-5.016 11.748h-3.63l-4.983-11.715-.066 2.376-1.023 13.5h-5.28l1.815-23.33h5.906z" fill="#222"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 237 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M504 256C504 119 393 8 256 8S8 119 8 256c0 123.78 90.69 226.38 209.25 245V327.69h-63V256h63v-54.64c0-62.15 37-96.48 93.67-96.48 27.14 0 55.52 4.84 55.52 4.84v61h-31.28c-30.8 0-40.41 19.12-40.41 38.73V256h68.78l-11 71.69h-57.78V501C413.31 482.38 504 379.78 504 256z"/></svg>
|
||||
|
Before Width: | Height: | Size: 524 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M416 32H31.9C14.3 32 0 46.5 0 64.3v383.4C0 465.5 14.3 480 31.9 480H416c17.6 0 32-14.5 32-32.3V64.3c0-17.8-14.4-32.3-32-32.3zM135.4 416H69V202.2h66.5V416zm-33.2-243c-21.3 0-38.5-17.3-38.5-38.5S80.9 96 102.2 96c21.2 0 38.5 17.3 38.5 38.5 0 21.3-17.2 38.5-38.5 38.5zm282.1 243h-66.4V312c0-24.8-.5-56.7-34.5-56.7-34.6 0-39.9 27-39.9 54.9V416h-66.4V202.2h63.7v29.2h.9c8.9-16.8 30.6-34.5 62.9-34.5 67.2 0 79.7 44.3 79.7 101.9V416z"/></svg>
|
||||
|
Before Width: | Height: | Size: 684 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M94.12 315.1c0 25.9-21.16 47.06-47.06 47.06S0 341 0 315.1c0-25.9 21.16-47.06 47.06-47.06h47.06v47.06zm23.72 0c0-25.9 21.16-47.06 47.06-47.06s47.06 21.16 47.06 47.06v117.84c0 25.9-21.16 47.06-47.06 47.06s-47.06-21.16-47.06-47.06V315.1zm47.06-188.98c-25.9 0-47.06-21.16-47.06-47.06S139 32 164.9 32s47.06 21.16 47.06 47.06v47.06H164.9zm0 23.72c25.9 0 47.06 21.16 47.06 47.06s-21.16 47.06-47.06 47.06H47.06C21.16 243.96 0 222.8 0 196.9s21.16-47.06 47.06-47.06H164.9zm188.98 47.06c0-25.9 21.16-47.06 47.06-47.06 25.9 0 47.06 21.16 47.06 47.06s-21.16 47.06-47.06 47.06h-47.06V196.9zm-23.72 0c0 25.9-21.16 47.06-47.06 47.06-25.9 0-47.06-21.16-47.06-47.06V79.06c0-25.9 21.16-47.06 47.06-47.06 25.9 0 47.06 21.16 47.06 47.06V196.9zM283.1 385.88c25.9 0 47.06 21.16 47.06 47.06 0 25.9-21.16 47.06-47.06 47.06-25.9 0-47.06-21.16-47.06-47.06v-47.06h47.06zm0-23.72c-25.9 0-47.06-21.16-47.06-47.06 0-25.9 21.16-47.06 47.06-47.06h117.84c25.9 0 47.06 21.16 47.06 47.06 0 25.9-21.16 47.06-47.06 47.06H283.1z"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.0 KiB |
@@ -1,7 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="288" height="113" viewBox="0 0 288 113">
|
||||
<g fill-rule="evenodd" transform="translate(28 24)">
|
||||
<path d="M154.289688,15.9951456 L163.366979,15.9951456 L163.366979,49.1019417 L154.289688,49.1019417 L154.289688,15.9951456 Z M48.1533333,15.9951456 L48.1533333,33.8834951 C48.1533333,38.276699 46.1388542,40.8495146 42.1098958,40.8495146 C38.0809375,40.8495146 36.0664583,38.276699 36.0664583,33.8834951 L36.0664583,15.9951456 L26.8920833,15.9951456 L26.8920833,36.1650485 C26.8920833,44.684466 33.2267708,49.7330097 42.2069792,49.7330097 C51.1871875,49.7330097 57.521875,44.6601942 57.521875,36.1650485 L57.521875,15.9951456 L48.1533333,15.9951456 Z M111.912813,15.9951456 L111.912813,33.8834951 C111.912813,38.276699 109.898333,40.8495146 105.869375,40.8495146 C101.840417,40.8495146 99.8259375,38.276699 99.8259375,33.8834951 L99.8259375,15.9951456 L90.6515625,15.9951456 L90.6515625,36.1650485 C90.6515625,44.684466 96.98625,49.7330097 105.966458,49.7330097 C114.946667,49.7330097 121.281354,44.684466 121.281354,36.1650485 L121.281354,15.9951456 L111.912813,15.9951456 Z"/>
|
||||
<ellipse cx="158.683" cy="5.777" rx="5.776" ry="5.777"/>
|
||||
<path d="M126.766562,15.9951456 L126.766562,49.1019417 L135.843854,49.1019417 L135.843854,24.1504854 L148.513229,24.1504854 L148.513229,15.9951456 L126.766562,15.9951456 Z M223.000417,38.7621359 C221.35,40.6067961 219.044271,41.7961165 216.39875,41.7961165 L216.301667,41.7961165 C211.520312,41.7961165 208.607812,39.4174757 207.491354,35.7524272 L233,35.7524272 L233,32.4271845 C233,22.8883495 226.034271,15.2669903 215.937604,15.2669903 C206.399167,15.2669903 198.681042,22.961165 198.681042,32.5242718 C198.681042,42.0873786 205.840937,49.7815534 216.1075,49.7815534 L216.301667,49.7815534 C221.616979,49.7815534 225.840104,47.7669903 228.776875,44.7330097 L223.000417,38.7621359 Z M216.034687,23.2524272 C219.89375,23.2524272 222.733437,25 223.922708,28.4708738 L207.879687,28.4708738 C209.238854,25.3640777 212.175625,23.2524272 216.034687,23.2524272 Z M191.084271,38.7621359 C189.530937,40.2184466 187.516458,41.1407767 185.210729,41.1407767 L185.016562,41.1407767 C180.332292,41.0436893 176.764479,37.2815534 176.764479,32.5242718 C176.764479,27.8398058 180.332292,24.0048544 185.016562,23.907767 L185.210729,23.907767 C187.686354,23.907767 189.797917,25 191.35125,26.6504854 L197.588854,20.4126214 C194.555,17.2087379 190.259062,15.2669903 185.113646,15.2669903 L185.016562,15.2669903 C175.575208,15.3640777 168.05125,22.961165 168.05125,32.4271845 C168.05125,41.8932039 175.575208,49.5873786 185.016562,49.5873786 L185.113646,49.5873786 C189.967812,49.5873786 194.190937,47.7427184 197.224792,44.8058252 L191.084271,38.7621359 Z M72.1086458,15.9951456 L72.1086458,3.61650485 L63.0313542,3.61650485 L63.0313542,34.6359223 C63.0313542,45.461165 66.5263542,49.0291262 76.9628125,49.0291262 L84.75375,49.0291262 L84.75375,40.5825243 L78.2491667,40.5825243 C72.6426042,40.5825243 72.0115625,39.7572816 72.0115625,34.6116505 L72.0115625,24.1747573 L84.75375,24.1747573 L84.75375,16.0194175 C84.75375,15.9951456 72.1086458,15.9951456 72.1086458,15.9951456 Z M9.07729167,13.8106796 C9.07729167,10.6796117 10.9946875,8.8592233 13.5673958,8.8592233 C15.2178125,8.8592233 16.5041667,9.7815534 17.1352083,10.776699 L22.6446875,5.26699029 C19.805,1.31067961 16.0430208,0.0485436893 12.378125,0.0485436893 C5.679375,0.0485436893 0.169895833,5.9223301 0,13.7135922 L0,49.1262136 L9.07729167,49.1262136 L9.07729167,23.8834951 L20.824375,23.8834951 L20.824375,15.9951456 L9.07729167,15.9951456 L9.07729167,13.8106796 Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 8.7 KiB |
@@ -1 +0,0 @@
|
||||
<svg height="113" viewBox="0 0 256 113" width="256" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd" transform="translate(25 30)"><path d="m188.414599 9.40689655c-8.350365.60689655-15.030657 7.43448275-15.637957 15.77931035-.607299 9.862069 7.135767 18.0551724 16.852555 18.0551724 3.491971 0 8.19854-1.8206896 10.324088-5.462069v5.0068966h6.528467v-16.0827586c.303649-10.6206897-8.19854-18.05517244-18.067153-17.29655175zm11.083211 17.29655175c-.303649 5.6137931-5.162044 9.8620689-10.931387 9.1034483-4.402919-.6068966-7.89489-4.0965518-8.502189-8.4965518-.6073-5.9172414 3.947445-10.9241379 9.716788-10.9241379 5.162044 0 9.413139 4.0965517 9.716788 9.2551724z" fill-rule="nonzero"/><path d="m152.583942 9.40689655c-8.957665.30344828-16.548906 7.13103445-16.548906 17.14482755 0 9.1034483 7.439417 16.8413793 17.00438 16.8413793 6.680292 0 13.208759-3.9448275 15.789781-10.4689655l-6.528467-1.9724138c-1.214599 2.7310345-4.09927 5.0068966-7.287591 5.6137931-4.554745.9103449-8.80584-1.9724138-10.475913-5.0068965l25.354745-6.9793104c-.759124-6.675862-6.528467-15.47586202-17.308029-15.17241375zm-9.413139 16.08275865c0-3.4896552 2.125547-7.5862069 6.680292-9.2551724 5.162044-1.9724138 9.716788.6068965 11.690511 4.2482758z" fill-rule="nonzero"/><path d="m96.8642336 16.2344828v-6.97931039c-6.3766424 0-8.5021898 3.18620689-9.5649635 4.85517239v-3.7931034h-7.2875913v15.475862 16.8413794h7.4394161c0-10.0137931 0-9.862069 0-15.9310345 0-7.2827586 4.2510949-10.1655173 9.4131387-10.4689655z"/><path d="m29.9094891 29.737931-22.16642341-26.09655169h-7.59124087v38.99310349h7.89489051v-26.0965518l22.47007297 26.2482759h6.8321168v-39.14482759h-7.439416z"/><g fill-rule="nonzero"><path d="m131.632117.15172414h-6.983942v12.74482756c-1.670073-2.1241379-6.832117-3.94482756-11.235036-3.48965515-8.350365.60689655-15.0306572 7.43448275-15.6379565 15.77931035-.6072993 9.862069 7.1357665 18.0551724 16.8525545 18.0551724 3.643796 0 8.654015-1.6689655 10.324088-4.8551724v4.2482759h6.528467v-16.0827587c0-.1517241 0-.1517241 0-.3034482 0-.1517242 0-.1517242 0-.3034483zm-7.287591 26.55172416c-.30365 5.6137931-5.162044 9.8620689-10.931387 9.1034483-4.40292-.6068966-7.894891-4.0965518-8.50219-8.4965518-.607299-5.9172414 3.947445-10.9241379 9.716788-10.9241379 5.162044 0 9.413139 4.0965517 9.716789 9.2551724z"/><path d="m58.7562044 9.25517241c-9.4131387 0-17.0043796 7.58620689-17.0043796 16.99310349 0 9.4068965 7.5912409 16.9931034 17.0043796 16.9931034s17.0043795-7.5862069 17.0043795-16.9931034c-.1518248-9.4068966-7.7430657-16.99310349-17.0043795-16.99310349zm0 26.70344829c-5.3138686 0-9.7167883-4.4-9.7167883-9.7103448 0-5.3103449 4.4029197-9.7103449 9.7167883-9.7103449s9.7167883 4.4 9.7167883 9.7103449c0 5.4620689-4.4029197 9.7103448-9.7167883 9.7103448z"/></g></g></svg>
|
||||
|
Before Width: | Height: | Size: 2.7 KiB |
@@ -1,3 +0,0 @@
|
||||
<svg width="256" height="113" viewBox="0 0 256 113" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M39.0615 75H49.2664V63.3449H53.0262H53.1336L60.7604 75H72.4693L63.1237 61.1427C66.9908 59.3703 69.5152 55.2883 69.5152 50.4544C69.5152 43.2572 63.4997 37.5639 56.0876 37.5639H39.0615V75ZM49.2664 55.1809V46.8021H55.8191C57.7527 46.8021 59.364 48.3597 59.364 50.7767C59.364 53.5696 57.6452 55.1809 54.9597 55.1809H49.2664ZM83.7317 75H94.3663L96.7833 70.1124H112.144L114.561 75H125.196L104.464 35.0396L83.7317 75ZM100.221 62.217L104.464 53.5696L108.707 62.217H100.221ZM136.772 75H146.977V63.3449H150.737H150.844L158.471 75H170.18L160.834 61.1427C164.702 59.3703 167.226 55.2883 167.226 50.4544C167.226 43.2572 161.21 37.5639 153.798 37.5639H136.772V75ZM146.977 55.1809V46.8021H153.53C155.463 46.8021 157.075 48.3597 157.075 50.7767C157.075 53.5696 155.356 55.1809 152.67 55.1809H146.977ZM181.872 75H209.909V65.9767H192.077V60.2834H208.298V51.9046H192.077V46.7484H209.479V37.5639H181.872V75Z" fill="black"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1020 B |
@@ -1 +0,0 @@
|
||||
<svg height="113" viewBox="0 0 268 113" width="268" xmlns="http://www.w3.org/2000/svg"><g fill="#282828" fill-rule="evenodd" transform="translate(24 22)"><path d="m.9 25.4h12.7v25.4h-12.7z"/><path d="m30.1 0h12.7v25.4h-12.7z"/><path d="m15.5 0h12.7v50.8h-12.7z"/><path d="m85.8 35.5c0-8.7 7-15.8 15.8-15.8 8.7 0 15.8 7 15.8 15.8v.1c0 8.8-6.9 15.8-15.8 15.8-8.8 0-15.8-7.1-15.8-15.9zm24.2 0c0-4.7-3.6-8.6-8.4-8.6s-8.3 3.9-8.3 8.6 3.5 8.6 8.3 8.6c4.9 0 8.4-3.9 8.4-8.6z" fill-rule="nonzero"/><path d="m123.8 20.1h7.4v23.8h13.4v7.1h-20.8z"/><path d="m152 20.1h7.4v30.8h-7.4z"/><path d="m175.1 27.2h-8.8v-7.1h24.9v7.1h-8.8v23.8h-7.4v-23.8z"/><path d="m202.4 20.1h7.1l11.4 30.8h-7.5l-1.6-4.6h-11.8l-1.6 4.6h-7.4zm-.1 19.8h7.3l-3.6-10.4z" fill-rule="nonzero"/><path d="m55.5 45.2 4.9-4.9c2.5 3 5 4.1 9.2 4.1 2.5 0 3.8-1.2 3.8-2.9 0-2.1-2-2.5-6.4-3.6-4.5-1-9.7-2.7-9.7-9 0-5.4 4.7-9.5 11.3-9.5 5.7 0 9 1.9 12.3 5.5l-4.5 4.5c-2.2-2.5-4.3-3.5-8-3.5-2.5 0-3.7 1.4-3.7 2.7 0 1.9 2.5 2.3 6.3 3.2 4.6 1.1 10.1 2.7 10.1 9.3 0 5.8-4.9 10.1-11.7 10.1-6.9.2-11-2.5-13.9-6z"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 1.0 KiB |
@@ -1 +0,0 @@
|
||||
<svg height="113" viewBox="0 0 349 113" width="349" xmlns="http://www.w3.org/2000/svg"><g fill="#231f20" fill-rule="evenodd" transform="translate(25 41)"><path d="m128.36964 32.3683594h9.092624l-15.437393-20.5321875 13.258148-11.26640627h-5.102058l-19.651801 16.69593747v-16.69593747h-7.253002v31.79859377h7.253002v-10.76625l5.948542-5.053125z"/><path d="m57.7667238.56976563-12.5789022 24.23953127-12.3576329-24.23953127h-8.060892l4.1114923 8.06695312-8.4288164 16.23960935-12.39108066-24.30656247h-8.06089194l16.2092624 31.79859377h3.915952l10.5180103-20.2717969 10.3353345 20.2717969h3.8464837l16.5-31.79859377z"/><path d="m152.56518 29.4086719v-12.2409375h18.401372v-2.9596875h-18.401372v-10.67859378h20.302745l2.315608-2.95968749h-29.868782v31.79859377h29.868782l-2.315608-2.9596875z"/><path d="m277.754717 29.4086719v-12.2409375h18.403945v-2.9596875h-18.403945v-10.67859378h22.106346v-2.95968749h-29.356775v31.79859377h29.356775v-2.9596875z"/><path d="m88.0523156 32.3683594h8.0608919l-16.2092624-31.79601565h-4.3867924l-16.5 31.79601565h3.558319l3.3010292-6.3602344h18.9339622zm-20.6372213-9.3199219 8.0145798-15.44554687 7.8730703 15.44554687z" fill-rule="nonzero"/><path d="m256.196398.56976563v21.80062497l-20.814751-21.80062497h-8.68868v31.79859377h3.246999v-26.89757815l25.682676 26.89757815h3.820754v-31.79859377z"/><path d="m218.091767 15.9044531c-.295884-11.27671873-8.88422-15.9044531-19.443397-15.9044531h-.002572c-10.744426 0-19.456261 4.78757813-19.456261 16.5 0 11.7124219 8.711835 16.5 19.456261 16.5h.002572c10.456261 0 18.985421-4.5375 19.435678-15.5796094.012865-.2990625.020583-.6032812.020583-.9203906 0-.20625-.005146-.4047656-.012864-.5955469m-19.443397 14.2028907c-7.510291 0-11.536878-2.26875-11.536878-13.6073438 0-11.33859375 4.02916-13.60992188 11.536878-13.60992188 7.510292 0 11.536879 2.26875 11.536879 13.60992188 0 11.3411719-4.02916 13.6073438-11.536879 13.6073438" fill-rule="nonzero"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 1.9 KiB |
@@ -1,4 +0,0 @@
|
||||
<svg width="72" height="72" viewBox="0 0 72 72" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15 6C10.0294 6 6 10.0294 6 15V57C6 61.9706 10.0294 66 15 66H57C61.9706 66 66 61.9706 66 57V15C66 10.0294 61.9706 6 57 6H15ZM15 0H57C65.2843 0 72 6.71573 72 15V57C72 65.2843 65.2843 72 57 72H15C6.71573 72 0 65.2843 0 57V15C0 6.71573 6.71573 0 15 0Z" fill="white"/>
|
||||
<path d="M31.792 18.2812L49.723 36.0077L31.792 53.734L26.277 48.2796L38.693 36.0077L26.277 23.7355L31.792 18.2812Z" fill="white"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 508 B |
@@ -1,4 +0,0 @@
|
||||
<svg width="72" height="72" viewBox="0 0 72 72" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15 6C10.0294 6 6 10.0294 6 15V57C6 61.9706 10.0294 66 15 66H57C61.9706 66 66 61.9706 66 57V15C66 10.0294 61.9706 6 57 6H15ZM15 0H57C65.2843 0 72 6.71573 72 15V57C72 65.2843 65.2843 72 57 72H15C6.71573 72 0 65.2843 0 57V15C0 6.71573 6.71573 0 15 0Z" fill="black"/>
|
||||
<path d="M31.792 18.2812L49.723 36.0077L31.792 53.734L26.277 48.2796L38.693 36.0077L26.277 23.7355L31.792 18.2812Z" fill="black"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 508 B |
@@ -1,10 +0,0 @@
|
||||
.btn {
|
||||
border-radius: 3px;
|
||||
padding: 1em;
|
||||
border: 0;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
&:disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
@@ -1,299 +0,0 @@
|
||||
$headerHeight: 400px;
|
||||
.header {
|
||||
background: url("/static/images/jumbo.jpg");
|
||||
background-position: bottom center;
|
||||
background-size: cover;
|
||||
height: $headerHeight;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
transform: translateZ(0);
|
||||
|
||||
@media screen and (max-width: 1030px) {
|
||||
height: 300px;
|
||||
|
||||
video {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
.header__container {
|
||||
background: #3e7394;
|
||||
background: linear-gradient(
|
||||
120deg,
|
||||
rgba(#085078, 0.8) 10%,
|
||||
rgba(#85d8ce, 0.7) 90%
|
||||
);
|
||||
display: table;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
width: 100%;
|
||||
}
|
||||
.header__nav {
|
||||
position: absolute;
|
||||
left: 90px;
|
||||
top: 40px;
|
||||
|
||||
@media screen and (max-width: 810px) {
|
||||
left: auto;
|
||||
right: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
a {
|
||||
display: block;
|
||||
max-width: 180px;
|
||||
|
||||
@media screen and (max-width: 810px) {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
.header__headline {
|
||||
display: table-cell;
|
||||
padding: 0 1em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.header__title {
|
||||
color: #fff;
|
||||
display: inline-block;
|
||||
font-size: 2.75em;
|
||||
letter-spacing: -1px;
|
||||
line-height: 1.4em;
|
||||
margin: auto;
|
||||
max-width: 780px;
|
||||
font-family: "Lato", sans-serif;
|
||||
font-weight: 900;
|
||||
text-align: center;
|
||||
text-shadow: 0 2px 0 rgba(#000, 0.1);
|
||||
vertical-align: middle;
|
||||
|
||||
@media screen and (max-width: 1030px) {
|
||||
font-size: 2em;
|
||||
max-width: 640px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 810px) {
|
||||
display: block;
|
||||
font-size: 1.6em;
|
||||
max-width: 440px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 410px) {
|
||||
font-size: 1.4em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
.header__video-bg {
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
z-index: -2;
|
||||
|
||||
@media (min-aspect-ratio: 1 / 2) {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes spin {
|
||||
0% {
|
||||
top: 0;
|
||||
}
|
||||
50% {
|
||||
top: -($headerHeight);
|
||||
}
|
||||
50.0001% {
|
||||
top: $headerHeight;
|
||||
}
|
||||
100% {
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@-moz-keyframes spin {
|
||||
0% {
|
||||
top: 0;
|
||||
}
|
||||
50% {
|
||||
top: -($headerHeight);
|
||||
}
|
||||
50.0001% {
|
||||
top: $headerHeight;
|
||||
}
|
||||
100% {
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@-ms-keyframes spin {
|
||||
0% {
|
||||
top: 0;
|
||||
}
|
||||
50% {
|
||||
top: -($headerHeight);
|
||||
}
|
||||
50.0001% {
|
||||
top: $headerHeight;
|
||||
}
|
||||
100% {
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@-o-keyframes spin {
|
||||
0% {
|
||||
top: 0;
|
||||
}
|
||||
50% {
|
||||
top: -($headerHeight);
|
||||
}
|
||||
50.0001% {
|
||||
top: $headerHeight;
|
||||
}
|
||||
100% {
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
top: 0;
|
||||
}
|
||||
50% {
|
||||
top: -($headerHeight);
|
||||
}
|
||||
50.0001% {
|
||||
top: $headerHeight;
|
||||
}
|
||||
100% {
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes spin2 {
|
||||
0% {
|
||||
top: $headerHeight;
|
||||
}
|
||||
50% {
|
||||
top: 0;
|
||||
}
|
||||
99.99999% {
|
||||
top: -($headerHeight);
|
||||
}
|
||||
100% {
|
||||
top: $headerHeight;
|
||||
}
|
||||
}
|
||||
|
||||
@-moz-keyframes spin2 {
|
||||
0% {
|
||||
top: $headerHeight;
|
||||
}
|
||||
50% {
|
||||
top: 0;
|
||||
}
|
||||
99.99999% {
|
||||
top: -($headerHeight);
|
||||
}
|
||||
100% {
|
||||
top: $headerHeight;
|
||||
}
|
||||
}
|
||||
|
||||
@-ms-keyframes spin2 {
|
||||
0% {
|
||||
top: $headerHeight;
|
||||
}
|
||||
50% {
|
||||
top: 0;
|
||||
}
|
||||
99.99999% {
|
||||
top: -($headerHeight);
|
||||
}
|
||||
100% {
|
||||
top: $headerHeight;
|
||||
}
|
||||
}
|
||||
|
||||
@-o-keyframes spin2 {
|
||||
0% {
|
||||
top: $headerHeight;
|
||||
}
|
||||
50% {
|
||||
top: 0;
|
||||
}
|
||||
99.99999% {
|
||||
top: -($headerHeight);
|
||||
}
|
||||
100% {
|
||||
top: $headerHeight;
|
||||
}
|
||||
}
|
||||
|
||||
@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: calc(100% / 18);
|
||||
|
||||
@media screen and (min-width: 2000px) {
|
||||
width: 5%;
|
||||
}
|
||||
@media screen and (max-width: 1200px) {
|
||||
width: calc(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
.input {
|
||||
height: 45px;
|
||||
padding: 0.5em 1em;
|
||||
box-sizing: border-box;
|
||||
font-size: inherit;
|
||||
border: 1px solid rgba(0, 0, 0, 0.3);
|
||||
border-radius: 5px;
|
||||
background: #fff;
|
||||
outline: 0;
|
||||
color: rgba(0, 0, 0, 0.5);
|
||||
&:focus {
|
||||
border: 2px solid #3090de;
|
||||
color: rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
}
|
||||
.input::-webkit-input-placeholder {
|
||||
color: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
.input:-moz-placeholder {
|
||||
color: rgba(0, 0, 0, 0.3);
|
||||
opacity: 1;
|
||||
}
|
||||
.input::-moz-placeholder {
|
||||
color: rgba(0, 0, 0, 0.3);
|
||||
opacity: 1;
|
||||
}
|
||||
.input:-ms-input-placeholder {
|
||||
color: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
.input.has-error {
|
||||
border-color: rgba(226, 33, 112, 0.6);
|
||||
color: rgb(226, 33, 112);
|
||||
}
|
||||
.input.half {
|
||||
width: 48%;
|
||||
&.left {
|
||||
margin-right: 4%;
|
||||
}
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
.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;
|
||||
}
|
||||
|
||||
@-webkit-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;
|
||||
}
|
||||
}
|
||||
|
||||
@-moz-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;
|
||||
}
|
||||
}
|
||||
|
||||
@-ms-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;
|
||||
}
|
||||
}
|
||||
|
||||
@-o-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;
|
||||
}
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
@@ -1,602 +0,0 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=Lato:wght@900&family=Source+Sans+Pro:wght@400;600;700&display=swap");
|
||||
|
||||
$linkColor: #3090de;
|
||||
$textColor: #444;
|
||||
$feedWidth: 500px;
|
||||
$footerHeight: 50px;
|
||||
|
||||
@import "_input";
|
||||
@import "_button";
|
||||
@import "_header";
|
||||
@import "_loader";
|
||||
|
||||
body,
|
||||
html {
|
||||
margin: 0;
|
||||
font: 18px "Source Sans Pro", "Helvetica Neue", helvetica, arial, sans-serif;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
font-weight: 400;
|
||||
color: $textColor;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3 {
|
||||
margin: 0;
|
||||
margin-bottom: 0.5em;
|
||||
text-shadow: 1px 1px 10px rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.25em;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 1em;
|
||||
line-height: 1.75em;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $linkColor;
|
||||
text-decoration: none;
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
.site {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
flex-direction: column;
|
||||
}
|
||||
.container {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
section {
|
||||
background: #fff;
|
||||
padding: 2em 0;
|
||||
}
|
||||
.content {
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
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;
|
||||
background: #fcfcfc;
|
||||
}
|
||||
.row {
|
||||
max-width: 850px;
|
||||
margin: auto;
|
||||
overflow: auto;
|
||||
padding: 0 2em;
|
||||
margin-top: 2em;
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
.column {
|
||||
float: left;
|
||||
overflow: auto;
|
||||
padding: 0 1em;
|
||||
box-sizing: border-box;
|
||||
&:first-child {
|
||||
padding-left: 0;
|
||||
}
|
||||
&:last-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
img:first-child {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
.column3-5 {
|
||||
width: 100 * 0.2 * 3%;
|
||||
}
|
||||
.column2-5 {
|
||||
width: 100 * 0.2 * 2%;
|
||||
}
|
||||
.column1-2 {
|
||||
width: 50%;
|
||||
}
|
||||
.column {
|
||||
@media screen and (max-width: 700px) {
|
||||
width: 100%;
|
||||
float: none;
|
||||
margin-top: 1em;
|
||||
padding: 0;
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.btn {
|
||||
background: $linkColor;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
&.sending {
|
||||
.invite-form__loader {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
.controls-wrapper {
|
||||
display: flex;
|
||||
@media screen and (max-width: 450px) {
|
||||
flex-direction: column;
|
||||
.btn {
|
||||
margin: 0;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
.input-wrapper {
|
||||
position: relative;
|
||||
flex-grow: 1;
|
||||
width: 400px;
|
||||
@media screen and (max-width: 650px) {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
.invite-form {
|
||||
margin-top: 0.5rem;
|
||||
position: relative;
|
||||
.input {
|
||||
width: 100%;
|
||||
}
|
||||
.invite-form__input {
|
||||
padding-right: 2em;
|
||||
}
|
||||
.label {
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 0.9rem;
|
||||
opacity: 0.6;
|
||||
display: block;
|
||||
}
|
||||
.btn {
|
||||
height: 45px;
|
||||
font: inherit;
|
||||
border-radius: 15px;
|
||||
padding: 0.5rem 3rem;
|
||||
color: #fff;
|
||||
&:active {
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
.invite-form__loader {
|
||||
display: none;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
position: absolute;
|
||||
right: 1rem;
|
||||
top: 50%;
|
||||
margin-top: -5px;
|
||||
}
|
||||
}
|
||||
.code-of-conduct {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.membership-form {
|
||||
.input {
|
||||
margin: 8px 0px;
|
||||
}
|
||||
.btn {
|
||||
margin-top: 12px;
|
||||
}
|
||||
}
|
||||
.membership-form__loader {
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
@-webkit-keyframes drop {
|
||||
0% {
|
||||
transform: rotateX(90deg);
|
||||
}
|
||||
30% {
|
||||
transform: rotateX(45deg);
|
||||
}
|
||||
45% {
|
||||
transform: rotateX(0deg);
|
||||
}
|
||||
60% {
|
||||
transform: rotateX(45deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotateX(0deg);
|
||||
}
|
||||
}
|
||||
|
||||
@-moz-keyframes drop {
|
||||
0% {
|
||||
transform: rotateX(90deg);
|
||||
}
|
||||
30% {
|
||||
transform: rotateX(45deg);
|
||||
}
|
||||
45% {
|
||||
transform: rotateX(0deg);
|
||||
}
|
||||
60% {
|
||||
transform: rotateX(45deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotateX(0deg);
|
||||
}
|
||||
}
|
||||
|
||||
@-ms-keyframes drop {
|
||||
0% {
|
||||
transform: rotateX(90deg);
|
||||
}
|
||||
30% {
|
||||
transform: rotateX(45deg);
|
||||
}
|
||||
45% {
|
||||
transform: rotateX(0deg);
|
||||
}
|
||||
60% {
|
||||
transform: rotateX(45deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotateX(0deg);
|
||||
}
|
||||
}
|
||||
|
||||
@-o-keyframes drop {
|
||||
0% {
|
||||
transform: rotateX(90deg);
|
||||
}
|
||||
30% {
|
||||
transform: rotateX(45deg);
|
||||
}
|
||||
45% {
|
||||
transform: rotateX(0deg);
|
||||
}
|
||||
60% {
|
||||
transform: rotateX(45deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotateX(0deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes drop {
|
||||
0% {
|
||||
transform: rotateX(90deg);
|
||||
}
|
||||
30% {
|
||||
transform: rotateX(45deg);
|
||||
}
|
||||
45% {
|
||||
transform: rotateX(0deg);
|
||||
}
|
||||
60% {
|
||||
transform: rotateX(45deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotateX(0deg);
|
||||
}
|
||||
}
|
||||
.form--message {
|
||||
background: $linkColor;
|
||||
color: #fff;
|
||||
line-height: 40px;
|
||||
padding: 0 0.5em;
|
||||
margin-top: 4px;
|
||||
transform: rotateX(0deg);
|
||||
transform-origin: 100% 0;
|
||||
animation: drop 0.6s linear;
|
||||
a {
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
.form.has-error {
|
||||
.form--message {
|
||||
background: rgb(226, 33, 112);
|
||||
}
|
||||
}
|
||||
.members {
|
||||
a {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
.member {
|
||||
width: 30px;
|
||||
vertical-align: middle;
|
||||
margin: 3px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.project-image__codestats {
|
||||
width: 143px;
|
||||
}
|
||||
|
||||
footer {
|
||||
padding: 0.5em 1em;
|
||||
z-index: 2;
|
||||
border-top: 1px solid #ececec;
|
||||
font-size: 1.5em;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
text-align: center;
|
||||
@media screen and (max-width: 940px) {
|
||||
display: block;
|
||||
}
|
||||
i {
|
||||
margin: 0 0.3em;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
}
|
||||
.contacts {
|
||||
line-height: normal;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
@media screen and (max-width: 940px) {
|
||||
margin-top: 1em;
|
||||
}
|
||||
}
|
||||
.sponsors {
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
}
|
||||
.sponsors__label {
|
||||
color: #ccc;
|
||||
font-size: 12px;
|
||||
text-align: left;
|
||||
}
|
||||
#email {
|
||||
font-size: 0.85rem;
|
||||
@media screen and (min-width: 400px) {
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
.sponsor {
|
||||
height: 60px;
|
||||
margin-right: 1em;
|
||||
vertical-align: middle;
|
||||
@media screen and (max-width: 940px) {
|
||||
margin-top: 1em;
|
||||
}
|
||||
}
|
||||
.social {
|
||||
height: 30px;
|
||||
margin-left: 0.5em;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
.feed {
|
||||
width: $feedWidth;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 5;
|
||||
background: #fff;
|
||||
border-left: 1px solid #e5e5e5;
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1200px) {
|
||||
.feed {
|
||||
width: 350px;
|
||||
}
|
||||
.content.with-feed {
|
||||
padding-right: 350px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 980px) {
|
||||
.feed {
|
||||
width: 0;
|
||||
}
|
||||
.content.with-feed {
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
.message {
|
||||
padding: 1em;
|
||||
padding-right: 2em;
|
||||
border-top: 1px solid #e5e5e5;
|
||||
position: relative;
|
||||
display: flex;
|
||||
}
|
||||
.message__content {
|
||||
flex: 1;
|
||||
}
|
||||
.message__body {
|
||||
font-weight: 400;
|
||||
font-size: 15px;
|
||||
line-height: 1.4em;
|
||||
}
|
||||
.message__image {
|
||||
width: 50px;
|
||||
margin-right: 1em;
|
||||
margin-top: 0.5em;
|
||||
text-align: center;
|
||||
a {
|
||||
outline: 0;
|
||||
}
|
||||
img {
|
||||
width: 50px;
|
||||
}
|
||||
}
|
||||
.message__icon {
|
||||
margin: 6px;
|
||||
font-size: 20px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
.fa-twitter {
|
||||
color: #55acee;
|
||||
}
|
||||
.fa-github {
|
||||
color: #333333;
|
||||
}
|
||||
.fa-slack {
|
||||
color: #dc005d;
|
||||
}
|
||||
}
|
||||
.message__details {
|
||||
font-size: 0.7em;
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
.message__meta {
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
.fader {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
rgba(0, 0, 0, 0) 0%,
|
||||
rgba(0, 0, 0, 0.15) 99%,
|
||||
rgba(0, 0, 0, 0.15) 100%
|
||||
);
|
||||
pointer-events: none;
|
||||
z-index: 2;
|
||||
}
|
||||
.bread {
|
||||
display: table;
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
.column {
|
||||
float: none;
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 700px) {
|
||||
.bread {
|
||||
display: block;
|
||||
.column {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
.bread-img {
|
||||
background: url("/static/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;
|
||||
}
|
||||
}
|
||||
.channels {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
td {
|
||||
padding: 0.4rem 0;
|
||||
}
|
||||
td:first-child {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
.channel {
|
||||
align-items: center;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 4px;
|
||||
color: #4a4a4a;
|
||||
display: inline-flex;
|
||||
font-size: 0.75rem;
|
||||
height: 2em;
|
||||
justify-content: center;
|
||||
line-height: 1.5;
|
||||
padding-left: 0.75em;
|
||||
padding-right: 0.75em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.channels tr:nth-child(1n) .channel {
|
||||
background-color: #ebfffc;
|
||||
color: #00947e;
|
||||
}
|
||||
.channels tr:nth-child(2n) .channel {
|
||||
background-color: #eef3fc;
|
||||
color: #2160c4;
|
||||
}
|
||||
.channels tr:nth-child(3n) .channel {
|
||||
background-color: #eef6fc;
|
||||
color: #1d72aa;
|
||||
}
|
||||
.channels tr:nth-child(4n) .channel {
|
||||
background-color: #effaf3;
|
||||
color: #257942;
|
||||
}
|
||||
.channels tr:nth-child(5n) .channel {
|
||||
background-color: #fffbeb;
|
||||
color: #947600;
|
||||
}
|
||||
.channels tr:nth-child(6n) .channel {
|
||||
background-color: #feecf0;
|
||||
color: #cc0f35;
|
||||
}
|
||||
.channel-members {
|
||||
font-size: 12px;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
.channel-topic {
|
||||
p {
|
||||
font-size: 0.8rem;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
border-left: 1px solid #e6e6e6;
|
||||
padding-left: 0.75rem;
|
||||
margin: 0;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
img {
|
||||
width: 20px;
|
||||
}
|
||||
}
|
||||
28
tailwind.config.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import type { Config } from 'tailwindcss';
|
||||
|
||||
const config = {
|
||||
darkMode: ['class'],
|
||||
content: ['./pages/**/*.{ts,tsx}', './components/**/*.{ts,tsx}', './app/**/*.{ts,tsx}', './src/**/*.{ts,tsx}'],
|
||||
prefix: '',
|
||||
|
||||
plugins: [],
|
||||
|
||||
theme: {
|
||||
screens: {
|
||||
xs: '440px', // => @media (min-width: 440px) { ... }
|
||||
sm: '640px', // => @media (min-width: 640px) { ... }
|
||||
md: '768px', // => @media (min-width: 768px) { ... }
|
||||
lg: '1024px', // => @media (min-width: 1024px) { ... }
|
||||
xl: '1280px', // => @media (min-width: 1280px) { ... }
|
||||
'2xl': '1536px', // => @media (min-width: 1536px) { ... }
|
||||
},
|
||||
|
||||
extend: {
|
||||
fontSize: {
|
||||
xxs: '0.625rem',
|
||||
},
|
||||
},
|
||||
},
|
||||
} satisfies Config;
|
||||
|
||||
export default config;
|
||||
@@ -1,21 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": false,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./*"]
|
||||
}
|
||||
},
|
||||
"typeRoots": ["./node_modules/@types", "./typings"],
|
||||
"exclude": ["node_modules"],
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "./typings/*"]
|
||||
"include": ["types/global.d.ts", "next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
||||
13
types/global.d.ts
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
interface Channel {
|
||||
id: string;
|
||||
name: string;
|
||||
topic: string;
|
||||
num_members: number;
|
||||
purpose: {
|
||||
value: string;
|
||||
creator: string;
|
||||
last_set: number;
|
||||
};
|
||||
messages_today: number;
|
||||
unique_members_today: number;
|
||||
}
|
||||
3
typings/global.d.ts
vendored
@@ -1,3 +0,0 @@
|
||||
interface Window {
|
||||
GA_INITIALIZED: boolean;
|
||||
}
|
||||