├── .editorconfig
├── .eslintrc.js
├── .github
└── workflows
│ └── test-lint.yml
├── .gitignore
├── .prettierrc.js
├── .vscode
└── settings.json
├── README.md
├── bin
└── setup
│ └── contentful.js
├── components
├── __snapshots__
│ ├── footer.spec.tsx.snap
│ ├── nav.spec.tsx.snap
│ ├── not-found.spec.tsx.snap
│ └── subscription.spec.tsx.snap
├── footer.spec.tsx
├── footer.tsx
├── index.ts
├── nav.spec.tsx
├── nav.tsx
├── not-found.spec.tsx
├── not-found.tsx
├── subscription.spec.tsx
└── subscription.tsx
├── config.json
├── config
└── index.ts
├── core
├── api.spec.ts
├── api.ts
├── index.ts
├── integrations
│ ├── contentful.service.ts
│ ├── index.ts
│ └── typings.ts
├── models
│ ├── author.ts
│ ├── category.ts
│ ├── index.ts
│ └── post.ts
└── typings
│ └── index.ts
├── jest.config.js
├── next-env.d.ts
├── next.config.js
├── now.json
├── package.json
├── pages
├── 404.tsx
├── _app.tsx
├── _error.tsx
├── category
│ └── [id].tsx
├── index.tsx
└── post
│ └── [id].tsx
├── public
├── favicon.ico
└── zeit.svg
├── schemas
└── contentful.json
├── setup-contentful.png
├── styles
├── bootstrap-user-variables.scss
└── main.scss
├── tests
└── setupTests.ts
├── tsconfig.jest.json
├── tsconfig.json
└── yarn.lock
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | charset = utf-8
7 | trim_trailing_whitespace = true
8 | insert_final_newline = true
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parser: '@typescript-eslint/parser',
3 | parserOptions: {
4 | ecmaVersion: 2020,
5 | sourceType: 'module',
6 | ecmaFeatures: {
7 | jsx: true
8 | }
9 | },
10 | settings: {
11 | react: {
12 | version: 'detect'
13 | }
14 | },
15 | extends: [
16 | 'plugin:react/recommended',
17 | 'plugin:@typescript-eslint/recommended',
18 | 'prettier/@typescript-eslint',
19 | 'plugin:prettier/recommended',
20 | 'plugin:jsx-a11y/recommended',
21 | ],
22 | rules: {
23 | 'react/react-in-jsx-scope': 'off'
24 | }
25 | };
26 |
--------------------------------------------------------------------------------
/.github/workflows/test-lint.yml:
--------------------------------------------------------------------------------
1 | name: Test & Lint
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | types: [ opened, synchronize ]
8 |
9 | jobs:
10 | dependencies:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v2
14 | - run: yarn install --frozen-lockfile --check-files
15 | - uses: actions/cache@v1
16 | id: cache-dependencies
17 | with:
18 | path: '.'
19 | key: ${{ github.sha }}
20 |
21 | test-lint:
22 | runs-on: ubuntu-latest
23 | needs: dependencies
24 | steps:
25 | - uses: actions/cache@v1
26 | id: restore-dependencies
27 | with:
28 | path: '.'
29 | key: ${{ github.sha }}
30 | - run: yarn test
31 | - run: yarn lint
32 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | .env*
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | .now
28 | .vercel
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | semi: true,
3 | trailingComma: 'all',
4 | singleQuote: true,
5 | printWidth: 120,
6 | tabWidth: 2,
7 | };
8 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "cSpell.words": [
3 | "CONTENTFUL"
4 | ]
5 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Next.js Medium style boilerplate blog
2 |
3 | > This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/zeit/next.js/tree/canary/packages/create-next-app).
4 |
5 | ## Data Sources
6 |
7 | This project has been designed to support any data source, under the `core/` folder you can find the models and service structure.
8 |
9 | ### Contentful
10 |
11 | Contentful is the default integration supported at the moment, we also provided a setup script together with a schema to easily get it up and running.
12 |
13 | ## Template
14 |
15 | This project uses [Mudana](https://www.wowthemes.net/mundana-free-html-bootstrap-template/) to achieve the medium style blog.
16 |
17 | ## Getting Started
18 |
19 | ### Install dependencies
20 |
21 | ```
22 | $ git@github.com:maxigimenez/next-medium-blog-boilerplate.git
23 | $ yarn install
24 | ```
25 |
26 | ### Setup models
27 |
28 | #### Contentful
29 |
30 | This projects comes with a Contentful schema ready to be used. Using `yarn setup:contentful`:
31 |
32 | 
33 |
34 | This command will ask you for a space ID, and access tokens for the Contentful Management and Delivery API and then import the schema defined on "schemas/contentful.json".
35 |
36 | Once the script is done you will be able to launch the blog and see dummy information ready to be changed.
37 |
38 | ## Scripts
39 |
40 | ### `yarn dev`
41 |
42 | Run the project locally. Then open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
43 |
44 | ## Deploy
45 |
46 | The repository comes with a simple `now.json` configuration, so we recommend to use [Zeit.co](https://zeit.co) to host the blog.
47 |
--------------------------------------------------------------------------------
/bin/setup/contentful.js:
--------------------------------------------------------------------------------
1 | const spaceImport = require('contentful-import');
2 | const schema = require('../../schemas/contentful.json');
3 | const inquirer = require('inquirer');
4 | const chalk = require('chalk');
5 | const path = require('path');
6 | const { writeFileSync } = require('fs');
7 |
8 | const argv = require('yargs-parser')(process.argv.slice(2));
9 |
10 | console.log(`
11 | To set up this project you need to provide your Space ID
12 | and the belonging API access tokens.
13 | You can find all the needed information in your Contentful space under:
14 | ${chalk.yellow(
15 | `app.contentful.com ${chalk.red('->')} Space Settings ${chalk.red(
16 | '->'
17 | )} API keys`
18 | )}
19 | The ${chalk.green('Content Management API Token')}
20 | will be used to import and write data to your space.
21 | The ${chalk.green('Content Delivery API Token')}
22 | will be used to ship published production-ready content in your Gatsby app.
23 | The ${chalk.green('Content Preview API Token')}
24 | will be used to show not published data in your development environment.
25 | Ready? Let's do it! 🎉
26 | `);
27 |
28 | const questions = [
29 | {
30 | name: 'spaceId',
31 | message: 'Your Space ID',
32 | when: !argv.spaceId && !process.env.CONTENTFUL_SPACE_ID,
33 | validate: input =>
34 | /^[a-z0-9]{12}$/.test(input) ||
35 | 'Space ID must be 12 lowercase characters',
36 | },
37 | {
38 | name: 'managementToken',
39 | when: !argv.managementToken && !process.env.CONTENTFUL_MANAGEMENT_TOKEN,
40 | message: 'Your Content Management API access token',
41 | },
42 | {
43 | name: 'accessToken',
44 | when: !argv.accessToken && !process.env.CONTENTFUL_ACCESS_TOKEN,
45 | message: 'Your Content Delivery API access token',
46 | }
47 | ];
48 |
49 | inquirer
50 | .prompt(questions)
51 | .then(({ spaceId, managementToken, accessToken }) => {
52 | const {
53 | CONTENTFUL_SPACE_ID,
54 | CONTENTFUL_ACCESS_TOKEN,
55 | CONTENTFUL_MANAGEMENT_TOKEN
56 | } = process.env;
57 |
58 | // env vars are given precedence followed by args provided to the setup
59 | // followed by input given to prompts displayed by the setup script
60 | spaceId = CONTENTFUL_SPACE_ID || argv.spaceId || spaceId;
61 | managementToken = CONTENTFUL_MANAGEMENT_TOKEN || argv.managementToken || managementToken;
62 | accessToken = CONTENTFUL_ACCESS_TOKEN || argv.accessToken || accessToken;
63 |
64 | console.log('Writing config file...');
65 | const configFiles = [`.env`].map(file =>
66 | path.join(__dirname, '../..', file)
67 | );
68 |
69 | const fileContents =
70 | [
71 | `# Do NOT commit this file to source control`,
72 | `CONTENTFUL_SPACE_ID=${spaceId}`,
73 | `CONTENTFUL_ACCESS_TOKEN=${accessToken}`,
74 | ].join('\n') + '\n';
75 |
76 | configFiles.forEach(file => {
77 | writeFileSync(file, fileContents, 'utf8')
78 | console.log(`Config file ${chalk.yellow(file)} written`);
79 | })
80 | return { spaceId, managementToken };
81 | })
82 | .then(({ spaceId, managementToken }) =>
83 | spaceImport({ spaceId, managementToken, content: schema })
84 | )
85 | .then((_, error) => {
86 | console.log(
87 | `All set! You can now run ${chalk.yellow(
88 | 'yarn dev'
89 | )} to see it in action.`
90 | );
91 | })
92 | .catch(error => console.error(error));
93 |
--------------------------------------------------------------------------------
/components/__snapshots__/footer.spec.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`footer snapshots render 1`] = `
4 |
46 | `;
47 |
--------------------------------------------------------------------------------
/components/__snapshots__/nav.spec.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`nav snapshots render if categories undefined 1`] = `
4 |
61 | `;
62 |
63 | exports[`nav snapshots render with categories 1`] = `
64 |
152 | `;
153 |
154 | exports[`nav snapshots render without categories 1`] = `
155 |
212 | `;
213 |
--------------------------------------------------------------------------------
/components/__snapshots__/not-found.spec.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`not-found snapshots render default 1`] = `
4 |
5 |
6 |
7 | Demo Blog - Next.js medium style boilerplate
8 |
9 |
10 |
18 |
21 | 404
22 |
23 |
26 |
30 | The page you requested was not found.
31 |
32 |
33 |
34 |
35 | `;
36 |
--------------------------------------------------------------------------------
/components/__snapshots__/subscription.spec.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`subscription snapshots render default 1`] = `
4 |
7 |
10 |
13 |
16 | Become a member
17 |
18 | Get the latest news right in your inbox. We never spam!
19 |
20 |
23 |
26 |
29 |
37 |
38 |
41 |
49 |
50 |
51 |
52 |
53 |
54 | `;
55 |
--------------------------------------------------------------------------------
/components/footer.spec.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 | import { Footer } from './footer';
4 |
5 | describe('footer', () => {
6 | describe('snapshots', () => {
7 | test('render', () => {
8 | const component = shallow();
9 | expect(component).toMatchSnapshot();
10 | });
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/components/footer.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import config from '@/config';
3 |
4 | export const Footer = () => (
5 |
32 | );
33 |
--------------------------------------------------------------------------------
/components/index.ts:
--------------------------------------------------------------------------------
1 | export * from './footer';
2 | export * from './nav';
3 | export * from './not-found';
4 | export * from './subscription';
5 |
--------------------------------------------------------------------------------
/components/nav.spec.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 | import { Nav } from './nav';
4 |
5 | describe('nav', () => {
6 | describe('snapshots', () => {
7 | test('render without categories', () => {
8 | const component = shallow();
9 | expect(component).toMatchSnapshot();
10 | });
11 |
12 | test('render with categories', () => {
13 | const categories = [
14 | {
15 | name: 'Category 1',
16 | slug: 'category-1',
17 | },
18 | {
19 | name: 'Category 2',
20 | slug: 'category-2',
21 | },
22 | ];
23 | const component = shallow();
24 | expect(component).toMatchSnapshot();
25 | });
26 |
27 | test('render if categories undefined', () => {
28 | const component = shallow();
29 | expect(component).toMatchSnapshot();
30 | });
31 | });
32 | });
33 |
--------------------------------------------------------------------------------
/components/nav.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Link from 'next/link';
3 | import config from '@/config';
4 | import { Category } from '@/core/models';
5 |
6 | type Props = {
7 | categories: Category[];
8 | };
9 |
10 | export const Nav = ({ categories }: Props) => (
11 |
59 | );
60 |
--------------------------------------------------------------------------------
/components/not-found.spec.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 | import { NotFound } from './not-found';
4 |
5 | describe('not-found', () => {
6 | describe('snapshots', () => {
7 | test('render default', () => {
8 | const component = shallow();
9 | expect(component).toMatchSnapshot();
10 | });
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/components/not-found.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Head from 'next/head';
3 | import config from '@/config';
4 |
5 | export const NotFound = () => (
6 | <>
7 |
8 | {config.title}
9 |
10 |
11 |
404
12 |
13 |
14 | The page you requested was not found.
15 |
16 |
17 |
18 | >
19 | );
20 |
--------------------------------------------------------------------------------
/components/subscription.spec.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 | import { Subscription } from './subscription';
4 |
5 | describe('subscription', () => {
6 | describe('snapshots', () => {
7 | let component;
8 | beforeEach(() => {
9 | component = shallow();
10 | });
11 |
12 | test('render default', () => {
13 | expect(component).toMatchSnapshot();
14 | });
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/components/subscription.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import config from '@/config';
3 |
4 | interface AlertMeta {
5 | class: string;
6 | message: string;
7 | }
8 |
9 | export const Subscription = () => {
10 | const [email, setEmail] = useState('');
11 | const [blocked, setBlocked] = useState(false);
12 | const [alert, setAlert] = useState();
13 |
14 | const subscribe = async () => {
15 | setBlocked(true);
16 | setAlert(null);
17 | try {
18 | const data = await fetch(config.subscription.url, {
19 | method: 'POST',
20 | body: JSON.stringify({ email, date: new Date() }),
21 | headers: {
22 | 'Content-Type': 'application/json',
23 | },
24 | });
25 | const json = await data.json();
26 | if (json) {
27 | setAlert({
28 | class: 'success',
29 | message: config.subscription.success,
30 | });
31 | }
32 | } catch (e) {
33 | setAlert({
34 | class: 'danger',
35 | message: config.subscription.error,
36 | });
37 | setBlocked(false);
38 | }
39 | };
40 |
41 | return (
42 |
43 |
44 |
45 |
Become a member
46 | Get the latest news right in your inbox. We never spam!
47 |
48 |
49 | {alert && (
50 |
51 | {alert.message}
52 |
53 | )}
54 |
55 |
56 |
57 | setEmail(e.target.value)}
60 | type="email"
61 | className="form-control"
62 | placeholder="Enter your e-mail address"
63 | disabled={blocked}
64 | />
65 |
66 |
67 |
70 |
71 |
72 |
73 |
74 |
75 | );
76 | };
77 |
--------------------------------------------------------------------------------
/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Demo Blog",
3 | "title": "Demo Blog - Next.js medium style boilerplate",
4 | "domain": "",
5 | "googleAnalytics": "UA-133302217-1",
6 | "subscription": {
7 | "enabled": true,
8 | "url": "https://sheetapi.co/apis/8oSEuumQgFaBfkbdNktgtw",
9 | "success": "Thank you for signing up. You will be the first to know about new releases",
10 | "error": "An error occurred, please try again"
11 | },
12 | "integration": "contentful"
13 | }
14 |
--------------------------------------------------------------------------------
/config/index.ts:
--------------------------------------------------------------------------------
1 | import config from '../config.json';
2 | import { Integrations } from '@/core/typings';
3 |
4 | interface Config {
5 | name: string;
6 | title: string;
7 | domain: string;
8 | googleAnalytics?: string;
9 | subscription: {
10 | enabled: boolean;
11 | url: string;
12 | success: string;
13 | error: string;
14 | };
15 | integration: Integrations;
16 | }
17 |
18 | export default config as Config;
19 |
--------------------------------------------------------------------------------
/core/api.spec.ts:
--------------------------------------------------------------------------------
1 | import { API } from './api';
2 | import { ContentfulService } from './integrations';
3 |
4 | /**
5 | * @TODO move into a mocks folder
6 | */
7 | jest.mock('./integrations', () => {
8 | return {
9 | ContentfulService: jest.fn(),
10 | };
11 | });
12 |
13 | describe('API', () => {
14 | it('should be defined', () => {
15 | expect(API).toBeDefined();
16 | });
17 |
18 | it('should be able to intialize with default integration', () => {
19 | const api = new API();
20 | expect(api).toBeInstanceOf(API);
21 | expect(ContentfulService).toHaveBeenCalledTimes(1);
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/core/api.ts:
--------------------------------------------------------------------------------
1 | import { Post, Category } from './models';
2 | import { ContentfulService, IntegrationService } from './integrations';
3 | import { Integrations } from './typings';
4 | import config from '@/config';
5 |
6 | export class API {
7 | private _client: IntegrationService;
8 |
9 | constructor() {
10 | const integration = this._resolveIntegration();
11 | switch (integration) {
12 | case Integrations.CONTENTFUL:
13 | this._client = new ContentfulService();
14 | break;
15 | default:
16 | throw new Error(`Invalid integration: ${integration}`);
17 | }
18 | }
19 |
20 | getPosts(): Promise {
21 | return this._client.getPosts();
22 | }
23 |
24 | getPostBySlug(slug: string): Promise {
25 | return this._client.getPostBySlug(slug);
26 | }
27 |
28 | getPostsByCategory(categorySlug: string): Promise {
29 | return this._client.getPostsByCategory(categorySlug);
30 | }
31 |
32 | getCategories(): Promise {
33 | return this._client.getCategories();
34 | }
35 |
36 | getCategory(slug: string): Promise {
37 | return this._client.getCategory(slug);
38 | }
39 |
40 | getPostsPaths(): Promise {
41 | return this._client.getPostsPaths();
42 | }
43 |
44 | getCategoriesPaths(): Promise {
45 | return this._client.getCategoriesPaths();
46 | }
47 |
48 | private _resolveIntegration(): Integrations {
49 | if (config.integration && Object.values(Integrations).includes(config.integration)) {
50 | return config.integration;
51 | }
52 | return Integrations.CONTENTFUL;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/core/index.ts:
--------------------------------------------------------------------------------
1 | export * from './api';
2 |
--------------------------------------------------------------------------------
/core/integrations/contentful.service.ts:
--------------------------------------------------------------------------------
1 | import { createClient } from 'contentful';
2 | import { IntegrationService } from './typings';
3 | import { Post, Category } from '../models';
4 | import readingTime from 'reading-time';
5 | import dayjs from 'dayjs';
6 |
7 | enum ContentType {
8 | POST = 'post',
9 | CATEGORY = 'category',
10 | }
11 |
12 | export class ContentfulService implements IntegrationService {
13 | private _client = createClient({
14 | space: process.env.CONTENTFUL_SPACE_ID,
15 | accessToken: process.env.CONTENTFUL_ACCESS_TOKEN,
16 | });
17 |
18 | getPosts(): Promise {
19 | return this._getPosts().then((items) => items.map((item) => createPost(item)));
20 | }
21 |
22 | getPostBySlug(slug: string): Promise {
23 | return new Promise(async (resolve, reject) => {
24 | try {
25 | const { items }: any = await this._client.getEntries({
26 | content_type: ContentType.POST,
27 | 'fields.slug[in]': slug,
28 | });
29 | const [post] = items;
30 | if (!post) {
31 | return reject();
32 | }
33 | return resolve(createPost(post));
34 | } catch (e) {
35 | return reject(e);
36 | }
37 | });
38 | }
39 |
40 | getPostsByCategory(categorySlug: string): Promise {
41 | return new Promise(async (resolve, reject) => {
42 | try {
43 | const { items } = await this._client.getEntries({
44 | content_type: ContentType.POST,
45 | 'fields.category.fields.slug[match]': categorySlug,
46 | 'fields.category.sys.contentType.sys.id': ContentType.CATEGORY,
47 | order: '-fields.publishedAt',
48 | });
49 | resolve(items.map((item) => createPost(item)));
50 | } catch (e) {
51 | return reject(e);
52 | }
53 | });
54 | }
55 |
56 | getCategories(): Promise {
57 | return this._getCategories().then((items) => items.map((item) => createCategory(item)));
58 | }
59 |
60 | getCategory(slug: string): Promise {
61 | return new Promise(async (resolve, reject) => {
62 | try {
63 | const { items }: any = await this._client.getEntries({
64 | content_type: ContentType.CATEGORY,
65 | 'fields.slug[in]': slug,
66 | });
67 | const [category] = items;
68 | if (!category) {
69 | return reject();
70 | }
71 | return resolve(createCategory(category));
72 | } catch (e) {
73 | return reject(e);
74 | }
75 | });
76 | }
77 |
78 | getPostsPaths(): Promise {
79 | return this._getPosts('fields.slug').then((items) => items.map((item) => item.fields.slug));
80 | }
81 |
82 | getCategoriesPaths(): Promise {
83 | return this._getCategories('fields.slug').then((items) => items.map((item) => item.fields.slug));
84 | }
85 |
86 | private _getPosts(select?: string): Promise {
87 | return new Promise(async (resolve, reject) => {
88 | try {
89 | const { items } = await this._client.getEntries({
90 | content_type: ContentType.POST,
91 | order: '-fields.publishedAt',
92 | select: select,
93 | });
94 | resolve(items);
95 | } catch (e) {
96 | return reject(e);
97 | }
98 | });
99 | }
100 |
101 | private _getCategories(select?: string): Promise {
102 | return new Promise(async (resolve, reject) => {
103 | try {
104 | const { items } = await this._client.getEntries({
105 | content_type: ContentType.CATEGORY,
106 | select: select,
107 | });
108 | resolve(items);
109 | } catch (e) {
110 | return reject(e);
111 | }
112 | });
113 | }
114 | }
115 |
116 | const createPost = (data: any): Post => {
117 | const { fields } = data;
118 | const { title, slug, shortBody, body, publishedAt, hero } = fields;
119 | return {
120 | title,
121 | slug,
122 | shortBody,
123 | body,
124 | publishedAt: dayjs(publishedAt).format('MMMM DD'),
125 | heroImage: hero.fields.file.url,
126 | author: {
127 | name: fields.author.fields.name,
128 | photo: fields.author.fields.photo.fields.file.url,
129 | },
130 | category: {
131 | name: fields.category.fields.name,
132 | slug: fields.category.fields.slug,
133 | },
134 | readingTime: readingTime(body).text,
135 | };
136 | };
137 |
138 | const createCategory = (data: any): Category => {
139 | const { name, slug } = data.fields;
140 | return {
141 | name,
142 | slug,
143 | };
144 | };
145 |
--------------------------------------------------------------------------------
/core/integrations/index.ts:
--------------------------------------------------------------------------------
1 | export * from './contentful.service';
2 | export * from './typings';
3 |
--------------------------------------------------------------------------------
/core/integrations/typings.ts:
--------------------------------------------------------------------------------
1 | import { Post, Category } from '../models';
2 |
3 | export interface IntegrationService {
4 | getPosts(): Promise;
5 | getPostBySlug(slug: string): Promise;
6 | getPostsByCategory(categorySlug: string): Promise;
7 | getCategories(): Promise;
8 | getCategory(slug: string): Promise;
9 | getPostsPaths(): Promise;
10 | getCategoriesPaths(): Promise;
11 | }
12 |
--------------------------------------------------------------------------------
/core/models/author.ts:
--------------------------------------------------------------------------------
1 | export interface Author {
2 | name: string;
3 | photo: string;
4 | }
5 |
--------------------------------------------------------------------------------
/core/models/category.ts:
--------------------------------------------------------------------------------
1 | export interface Category {
2 | name: string;
3 | slug: string;
4 | }
5 |
--------------------------------------------------------------------------------
/core/models/index.ts:
--------------------------------------------------------------------------------
1 | export * from './author';
2 | export * from './category';
3 | export * from './post';
4 |
--------------------------------------------------------------------------------
/core/models/post.ts:
--------------------------------------------------------------------------------
1 | import { Category } from './category';
2 | import { Author } from './author';
3 |
4 | export interface Post {
5 | title: string;
6 | slug: string;
7 | heroImage: string;
8 | shortBody: string;
9 | body: string;
10 | publishedAt: string;
11 | author: Author;
12 | category: Category;
13 | readingTime: string;
14 | }
15 |
--------------------------------------------------------------------------------
/core/typings/index.ts:
--------------------------------------------------------------------------------
1 | export enum Integrations {
2 | CONTENTFUL = 'contentful',
3 | }
4 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | const { pathsToModuleNameMapper } = require("ts-jest/utils");
2 | const { compilerOptions } = require('./tsconfig.json');
3 |
4 | module.exports = {
5 | testEnvironment: "node",
6 | roots: [
7 | "/components",
8 | "/core",
9 | "/config",
10 | "/pages"
11 | ],
12 | preset: 'ts-jest',
13 | setupFilesAfterEnv: ["/tests/setupTests.ts"],
14 | transform: {
15 | "^.+\\.tsx?$": "ts-jest"
16 | },
17 | testMatch: [
18 | "**/*.(test|spec).(ts|tsx)"
19 | ],
20 | moduleFileExtensions: [
21 | "ts",
22 | "tsx",
23 | "js",
24 | "jsx"
25 | ],
26 | testPathIgnorePatterns: ["/.next/", "/node_modules/"],
27 | snapshotSerializers: ["enzyme-to-json/serializer"],
28 | // https://github.com/zeit/next.js/issues/8663#issue-490553899
29 | globals: {
30 | // we must specify a custom tsconfig for tests because we need the typescript transform
31 | // to transform jsx into js rather than leaving it jsx such as the next build requires. you
32 | // can see this setting in tsconfig.jest.json -> "jsx": "react"
33 | "ts-jest": {
34 | tsConfig: "/tsconfig.jest.json"
35 | }
36 | },
37 | coverageReporters: ["text", "html"],
38 | moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, {
39 | prefix: '/'
40 | }),
41 | }
42 |
--------------------------------------------------------------------------------
/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | CONTENTFUL_SPACE_ID: process.env.CONTENTFUL_SPACE_ID,
4 | CONTENTFUL_ACCESS_TOKEN: process.env.CONTENTFUL_ACCESS_TOKEN
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/now.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 2
3 | }
4 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "next-medium-blog-boilerplate",
3 | "version": "1.0.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "setup:contentful": "node bin/setup/contentful.js",
10 | "test": "jest",
11 | "test:watch": "jest --watch",
12 | "test:coverage": "jest --coverage",
13 | "lint:fix": "yarn lint --fix",
14 | "lint": "eslint '{pages,components,config,core}/**/*.{js,ts,tsx}' --quiet"
15 | },
16 | "dependencies": {
17 | "bootstrap": "^4.4.1",
18 | "contentful": "^7.14.2",
19 | "dayjs": "^1.8.24",
20 | "next": "^9.5.4",
21 | "react": "^16.13.1",
22 | "react-dom": "^16.13.1",
23 | "react-markdown": "^4.3.1",
24 | "reading-time": "^1.2.0"
25 | },
26 | "devDependencies": {
27 | "@types/enzyme": "^3.10.5",
28 | "@types/enzyme-adapter-react-16": "^1.0.6",
29 | "@types/jest": "^25.2.1",
30 | "@types/node": "^13.11.1",
31 | "@types/react": "^16.9.34",
32 | "@typescript-eslint/eslint-plugin": "^3.6.1",
33 | "@typescript-eslint/parser": "^3.6.1",
34 | "chalk": "^4.0.0",
35 | "contentful-import": "^7.7.8",
36 | "enzyme": "^3.11.0",
37 | "enzyme-adapter-react-16": "^1.15.2",
38 | "enzyme-to-json": "^3.4.4",
39 | "eslint": "^7.5.0",
40 | "eslint-config-prettier": "^6.11.0",
41 | "eslint-plugin-jsx-a11y": "^6.3.1",
42 | "eslint-plugin-prettier": "^3.1.4",
43 | "eslint-plugin-react": "^7.20.3",
44 | "eslint-plugin-react-hooks": "^4.0.8",
45 | "inquirer": "^7.1.0",
46 | "jest": "^25.5.4",
47 | "prettier": "^2.0.5",
48 | "sass": "^1.26.3",
49 | "ts-jest": "^25.5.0",
50 | "typescript": "^3.8.3"
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/pages/404.tsx:
--------------------------------------------------------------------------------
1 | import { NotFound } from '@/components';
2 |
3 | const Page404 = () => ;
4 | export default Page404;
5 |
--------------------------------------------------------------------------------
/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import App from 'next/app';
2 | import Head from 'next/head';
3 |
4 | import '../styles/main.scss';
5 |
6 | import { Nav, Footer } from '@/components';
7 | import config from '@/config';
8 | import { API } from '@/core/api';
9 | import { Category } from '@/core/models';
10 |
11 | const CustomApp = ({
12 | Component,
13 | pageProps,
14 | categories,
15 | isErrorPage,
16 | }: {
17 | Component: any;
18 | pageProps: any;
19 | categories: Category[];
20 | isErrorPage: boolean;
21 | }) => {
22 | return (
23 | <>
24 |
25 |
29 | {/* Enable google analytics tracking */}
30 | {!!config.googleAnalytics && (
31 |
39 | )}
40 | {!!config.googleAnalytics && (
41 |
42 | )}
43 |
44 |
45 | {!isErrorPage && }
46 |
47 |
48 |
49 | {!isErrorPage && }
50 | >
51 | );
52 | };
53 |
54 | CustomApp.getInitialProps = async (context) => {
55 | const apiRef = new API();
56 | const appProps = await App.getInitialProps(context);
57 | const categories = await apiRef.getCategories();
58 | const isErrorPage = context.ctx.res.statusCode === 404 || false;
59 | return { ...appProps, categories, isErrorPage };
60 | };
61 |
62 | export default CustomApp;
63 |
--------------------------------------------------------------------------------
/pages/_error.tsx:
--------------------------------------------------------------------------------
1 | import { NotFound } from '@/components';
2 |
3 | const Error = () => ;
4 | export default Error;
5 |
--------------------------------------------------------------------------------
/pages/category/[id].tsx:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 | import Link from 'next/link';
3 |
4 | import ReactMarkdown from 'react-markdown';
5 |
6 | import { API } from '@/core/api';
7 | import config from '@/config';
8 | import { Post, Category as CategoryModel } from '@/core/models';
9 | import { Subscription } from '@/components';
10 |
11 | type Props = {
12 | posts?: Post[];
13 | category?: CategoryModel;
14 | };
15 |
16 | const Category = ({ posts, category }: Props) => {
17 | const [first] = posts;
18 |
19 | return (
20 | <>
21 |
22 | {config.title}
23 |
24 |
25 |
26 |
27 |
28 |
29 | Featured in {category.name}
30 |
31 | {first && (
32 |
33 |
36 |
37 |
44 |
45 |
46 |
47 | {first.author.name} in {first.category.name}
48 |
49 |
50 | {first.publishedAt} · {first.readingTime}
51 |
52 |
53 |
54 |
55 | )}
56 |
57 | Latest
58 |
59 | {posts.map((post: Post) => {
60 | return (
61 |
62 |
63 |
70 |
71 |
72 | {post.author.name} in {post.category.name}
73 |
74 |
75 | {post.publishedAt} · {post.readingTime}
76 |
77 |
78 |

79 |
80 | );
81 | })}
82 |
83 |
84 |
85 |
86 | {config.subscription.enabled && (
87 |
88 |
89 |
90 | )}
91 | >
92 | );
93 | };
94 |
95 | export const getStaticPaths = async () => {
96 | const apiRef = new API();
97 | const slugs = await apiRef.getCategoriesPaths();
98 | return {
99 | paths: slugs.map((slug) => `/category/${slug}`),
100 | fallback: false,
101 | };
102 | };
103 |
104 | export const getStaticProps = async ({ params }) => {
105 | const apiRef = new API();
106 | const category = await apiRef.getCategory(params.id);
107 | const posts = await apiRef.getPostsByCategory(params.id);
108 | return {
109 | props: {
110 | posts,
111 | category,
112 | },
113 | revalidate: 1,
114 | };
115 | };
116 |
117 | export default Category;
118 |
--------------------------------------------------------------------------------
/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 | import Link from 'next/link';
3 | import ReactMarkdown from 'react-markdown';
4 |
5 | import { Post } from '@/core/models';
6 | import { API } from '@/core/api';
7 | import config from '@/config';
8 |
9 | const Home = ({ posts }: { posts: Post[] }) => {
10 | const [first] = posts;
11 |
12 | return (
13 | <>
14 |
15 | {config.title}
16 |
17 |
18 | {first && (
19 |
44 | )}
45 |
46 |
47 |
48 |
49 |
50 | All Stories
51 |
52 | {posts.map((post) => {
53 | return (
54 |
55 |
56 |
63 |
64 |
65 | {post.author.name} in {post.category.name}
66 |
67 |
68 | {post.publishedAt} · {post.readingTime}
69 |
70 |
71 |

72 |
73 | );
74 | })}
75 |
76 |
77 |
78 | >
79 | );
80 | };
81 |
82 | export const getStaticProps = async () => {
83 | const apiRef = new API();
84 | const posts = await apiRef.getPosts();
85 | return {
86 | props: {
87 | posts,
88 | },
89 | revalidate: 1,
90 | };
91 | };
92 |
93 | export default Home;
94 |
--------------------------------------------------------------------------------
/pages/post/[id].tsx:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 | import Link from 'next/link';
3 | import ReactMarkdown from 'react-markdown';
4 |
5 | import { Post as PostModel } from '@/core/models';
6 | import config from '@/config';
7 | import { API } from '@/core/api';
8 |
9 | import { Subscription } from '@/components';
10 |
11 | const Post = ({ post }: { post: PostModel }) => {
12 | return (
13 | <>
14 |
15 |
16 | {post.title} - {config.title}
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | {post.category.name}
40 |
41 |
42 |
43 |
{post.title}
44 |
45 |
46 |

54 |
55 | {post.author.name}{' '}
56 |
57 | {post.publishedAt} · {post.readingTime}
58 |
59 |
60 |
61 |
62 |
63 |

68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
Share this
79 |
80 |
85 |
86 |
87 |
88 | `,
89 | }}
90 | >
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 | {config.subscription.enabled &&
}
99 |
100 |
101 |
102 | >
103 | );
104 | };
105 |
106 | export const getStaticPaths = async () => {
107 | const apiRef = new API();
108 | const slugs = await apiRef.getPostsPaths();
109 | return {
110 | paths: slugs.map((slug) => `/post/${slug}`),
111 | fallback: false,
112 | };
113 | };
114 |
115 | export const getStaticProps = async ({ params }) => {
116 | const apiRef = new API();
117 | const post = await apiRef.getPostBySlug(params.id);
118 | return {
119 | props: {
120 | post,
121 | },
122 | revalidate: 1,
123 | };
124 | };
125 |
126 | export default Post;
127 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maxigimenez/next-medium-blog-boilerplate/b39bdc54932b9fdcc8803ead2440edf280501121/public/favicon.ico
--------------------------------------------------------------------------------
/public/zeit.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/schemas/contentful.json:
--------------------------------------------------------------------------------
1 | {
2 | "contentTypes": [
3 | {
4 | "sys": {
5 | "space": {
6 | "sys": {
7 | "type": "Link",
8 | "linkType": "Space",
9 | "id": "el2orhksvy4m"
10 | }
11 | },
12 | "id": "category",
13 | "type": "ContentType",
14 | "createdAt": "2020-04-19T21:28:03.385Z",
15 | "updatedAt": "2020-04-19T21:30:18.049Z",
16 | "environment": {
17 | "sys": {
18 | "id": "master",
19 | "type": "Link",
20 | "linkType": "Environment"
21 | }
22 | },
23 | "publishedVersion": 2,
24 | "publishedAt": "2020-04-19T21:30:18.049Z",
25 | "firstPublishedAt": "2020-04-19T21:30:18.049Z",
26 | "createdBy": {
27 | "sys": {
28 | "type": "Link",
29 | "linkType": "User",
30 | "id": "2b9r06bKrCs1SX7eGWjrer"
31 | }
32 | },
33 | "updatedBy": {
34 | "sys": {
35 | "type": "Link",
36 | "linkType": "User",
37 | "id": "2b9r06bKrCs1SX7eGWjrer"
38 | }
39 | },
40 | "publishedCounter": 1,
41 | "version": 3,
42 | "publishedBy": {
43 | "sys": {
44 | "type": "Link",
45 | "linkType": "User",
46 | "id": "2b9r06bKrCs1SX7eGWjrer"
47 | }
48 | }
49 | },
50 | "displayField": "name",
51 | "name": "Category",
52 | "description": "",
53 | "fields": [
54 | {
55 | "id": "name",
56 | "name": "name",
57 | "type": "Symbol",
58 | "localized": false,
59 | "required": true,
60 | "validations": [
61 | ],
62 | "disabled": false,
63 | "omitted": false
64 | },
65 | {
66 | "id": "slug",
67 | "name": "slug",
68 | "type": "Symbol",
69 | "localized": false,
70 | "required": true,
71 | "validations": [
72 | {
73 | "unique": true
74 | }
75 | ],
76 | "disabled": false,
77 | "omitted": false
78 | }
79 | ]
80 | },
81 | {
82 | "sys": {
83 | "space": {
84 | "sys": {
85 | "type": "Link",
86 | "linkType": "Space",
87 | "id": "el2orhksvy4m"
88 | }
89 | },
90 | "id": "author",
91 | "type": "ContentType",
92 | "createdAt": "2020-04-19T21:31:15.244Z",
93 | "updatedAt": "2020-04-19T21:31:15.761Z",
94 | "environment": {
95 | "sys": {
96 | "id": "master",
97 | "type": "Link",
98 | "linkType": "Environment"
99 | }
100 | },
101 | "publishedVersion": 1,
102 | "publishedAt": "2020-04-19T21:31:15.761Z",
103 | "firstPublishedAt": "2020-04-19T21:31:15.761Z",
104 | "createdBy": {
105 | "sys": {
106 | "type": "Link",
107 | "linkType": "User",
108 | "id": "2b9r06bKrCs1SX7eGWjrer"
109 | }
110 | },
111 | "updatedBy": {
112 | "sys": {
113 | "type": "Link",
114 | "linkType": "User",
115 | "id": "2b9r06bKrCs1SX7eGWjrer"
116 | }
117 | },
118 | "publishedCounter": 1,
119 | "version": 2,
120 | "publishedBy": {
121 | "sys": {
122 | "type": "Link",
123 | "linkType": "User",
124 | "id": "2b9r06bKrCs1SX7eGWjrer"
125 | }
126 | }
127 | },
128 | "displayField": "name",
129 | "name": "Author",
130 | "description": "",
131 | "fields": [
132 | {
133 | "id": "name",
134 | "name": "name",
135 | "type": "Symbol",
136 | "localized": false,
137 | "required": false,
138 | "validations": [
139 | ],
140 | "disabled": false,
141 | "omitted": false
142 | },
143 | {
144 | "id": "photo",
145 | "name": "photo",
146 | "type": "Link",
147 | "localized": false,
148 | "required": false,
149 | "validations": [
150 | ],
151 | "disabled": false,
152 | "omitted": false,
153 | "linkType": "Asset"
154 | }
155 | ]
156 | },
157 | {
158 | "sys": {
159 | "space": {
160 | "sys": {
161 | "type": "Link",
162 | "linkType": "Space",
163 | "id": "el2orhksvy4m"
164 | }
165 | },
166 | "id": "post",
167 | "type": "ContentType",
168 | "createdAt": "2020-04-19T21:32:18.581Z",
169 | "updatedAt": "2020-04-19T21:32:19.055Z",
170 | "environment": {
171 | "sys": {
172 | "id": "master",
173 | "type": "Link",
174 | "linkType": "Environment"
175 | }
176 | },
177 | "publishedVersion": 1,
178 | "publishedAt": "2020-04-19T21:32:19.055Z",
179 | "firstPublishedAt": "2020-04-19T21:32:19.055Z",
180 | "createdBy": {
181 | "sys": {
182 | "type": "Link",
183 | "linkType": "User",
184 | "id": "2b9r06bKrCs1SX7eGWjrer"
185 | }
186 | },
187 | "updatedBy": {
188 | "sys": {
189 | "type": "Link",
190 | "linkType": "User",
191 | "id": "2b9r06bKrCs1SX7eGWjrer"
192 | }
193 | },
194 | "publishedCounter": 1,
195 | "version": 2,
196 | "publishedBy": {
197 | "sys": {
198 | "type": "Link",
199 | "linkType": "User",
200 | "id": "2b9r06bKrCs1SX7eGWjrer"
201 | }
202 | }
203 | },
204 | "displayField": "title",
205 | "name": "Post",
206 | "description": "",
207 | "fields": [
208 | {
209 | "id": "title",
210 | "name": "title",
211 | "type": "Symbol",
212 | "localized": false,
213 | "required": true,
214 | "validations": [
215 | ],
216 | "disabled": false,
217 | "omitted": false
218 | },
219 | {
220 | "id": "slug",
221 | "name": "slug",
222 | "type": "Symbol",
223 | "localized": false,
224 | "required": true,
225 | "validations": [
226 | {
227 | "unique": true
228 | }
229 | ],
230 | "disabled": false,
231 | "omitted": false
232 | },
233 | {
234 | "id": "hero",
235 | "name": "hero",
236 | "type": "Link",
237 | "localized": false,
238 | "required": false,
239 | "validations": [
240 | ],
241 | "disabled": false,
242 | "omitted": false,
243 | "linkType": "Asset"
244 | },
245 | {
246 | "id": "shortBody",
247 | "name": "short body",
248 | "type": "Text",
249 | "localized": false,
250 | "required": true,
251 | "validations": [
252 | ],
253 | "disabled": false,
254 | "omitted": false
255 | },
256 | {
257 | "id": "body",
258 | "name": "body",
259 | "type": "Text",
260 | "localized": false,
261 | "required": true,
262 | "validations": [
263 | ],
264 | "disabled": false,
265 | "omitted": false
266 | },
267 | {
268 | "id": "publishedAt",
269 | "name": "published at",
270 | "type": "Date",
271 | "localized": false,
272 | "required": true,
273 | "validations": [
274 | ],
275 | "disabled": false,
276 | "omitted": false
277 | },
278 | {
279 | "id": "author",
280 | "name": "author",
281 | "type": "Link",
282 | "localized": false,
283 | "required": false,
284 | "validations": [
285 | ],
286 | "disabled": false,
287 | "omitted": false,
288 | "linkType": "Entry"
289 | },
290 | {
291 | "id": "category",
292 | "name": "category",
293 | "type": "Link",
294 | "localized": false,
295 | "required": false,
296 | "validations": [
297 | ],
298 | "disabled": false,
299 | "omitted": false,
300 | "linkType": "Entry"
301 | }
302 | ]
303 | }
304 | ],
305 | "editorInterfaces": [
306 | {
307 | "sys": {
308 | "id": "default",
309 | "type": "EditorInterface",
310 | "space": {
311 | "sys": {
312 | "id": "el2orhksvy4m",
313 | "type": "Link",
314 | "linkType": "Space"
315 | }
316 | },
317 | "version": 2,
318 | "createdAt": "2020-04-19T21:30:18.105Z",
319 | "createdBy": {
320 | "sys": {
321 | "id": "2b9r06bKrCs1SX7eGWjrer",
322 | "type": "Link",
323 | "linkType": "User"
324 | }
325 | },
326 | "updatedAt": "2020-04-19T21:30:18.986Z",
327 | "updatedBy": {
328 | "sys": {
329 | "id": "2b9r06bKrCs1SX7eGWjrer",
330 | "type": "Link",
331 | "linkType": "User"
332 | }
333 | },
334 | "contentType": {
335 | "sys": {
336 | "id": "category",
337 | "type": "Link",
338 | "linkType": "ContentType"
339 | }
340 | },
341 | "environment": {
342 | "sys": {
343 | "id": "master",
344 | "type": "Link",
345 | "linkType": "Environment"
346 | }
347 | }
348 | },
349 | "controls": [
350 | {
351 | "fieldId": "name",
352 | "widgetId": "singleLine",
353 | "widgetNamespace": "builtin"
354 | },
355 | {
356 | "fieldId": "slug",
357 | "widgetId": "singleLine",
358 | "widgetNamespace": "builtin"
359 | }
360 | ]
361 | },
362 | {
363 | "sys": {
364 | "id": "default",
365 | "type": "EditorInterface",
366 | "space": {
367 | "sys": {
368 | "id": "el2orhksvy4m",
369 | "type": "Link",
370 | "linkType": "Space"
371 | }
372 | },
373 | "version": 2,
374 | "createdAt": "2020-04-19T21:31:15.818Z",
375 | "createdBy": {
376 | "sys": {
377 | "id": "2b9r06bKrCs1SX7eGWjrer",
378 | "type": "Link",
379 | "linkType": "User"
380 | }
381 | },
382 | "updatedAt": "2020-04-19T21:31:16.112Z",
383 | "updatedBy": {
384 | "sys": {
385 | "id": "2b9r06bKrCs1SX7eGWjrer",
386 | "type": "Link",
387 | "linkType": "User"
388 | }
389 | },
390 | "contentType": {
391 | "sys": {
392 | "id": "author",
393 | "type": "Link",
394 | "linkType": "ContentType"
395 | }
396 | },
397 | "environment": {
398 | "sys": {
399 | "id": "master",
400 | "type": "Link",
401 | "linkType": "Environment"
402 | }
403 | }
404 | },
405 | "controls": [
406 | {
407 | "fieldId": "name",
408 | "widgetId": "singleLine",
409 | "widgetNamespace": "builtin"
410 | },
411 | {
412 | "fieldId": "photo",
413 | "widgetId": "assetLinkEditor",
414 | "widgetNamespace": "builtin"
415 | }
416 | ]
417 | },
418 | {
419 | "sys": {
420 | "id": "default",
421 | "type": "EditorInterface",
422 | "space": {
423 | "sys": {
424 | "id": "el2orhksvy4m",
425 | "type": "Link",
426 | "linkType": "Space"
427 | }
428 | },
429 | "version": 2,
430 | "createdAt": "2020-04-19T21:32:19.111Z",
431 | "createdBy": {
432 | "sys": {
433 | "id": "2b9r06bKrCs1SX7eGWjrer",
434 | "type": "Link",
435 | "linkType": "User"
436 | }
437 | },
438 | "updatedAt": "2020-04-19T21:32:19.559Z",
439 | "updatedBy": {
440 | "sys": {
441 | "id": "2b9r06bKrCs1SX7eGWjrer",
442 | "type": "Link",
443 | "linkType": "User"
444 | }
445 | },
446 | "contentType": {
447 | "sys": {
448 | "id": "post",
449 | "type": "Link",
450 | "linkType": "ContentType"
451 | }
452 | },
453 | "environment": {
454 | "sys": {
455 | "id": "master",
456 | "type": "Link",
457 | "linkType": "Environment"
458 | }
459 | }
460 | },
461 | "controls": [
462 | {
463 | "fieldId": "title",
464 | "widgetId": "singleLine",
465 | "widgetNamespace": "builtin"
466 | },
467 | {
468 | "fieldId": "slug",
469 | "widgetId": "singleLine",
470 | "widgetNamespace": "builtin"
471 | },
472 | {
473 | "fieldId": "hero",
474 | "widgetId": "assetLinkEditor",
475 | "widgetNamespace": "builtin"
476 | },
477 | {
478 | "fieldId": "shortBody",
479 | "widgetId": "markdown",
480 | "widgetNamespace": "builtin"
481 | },
482 | {
483 | "fieldId": "body",
484 | "widgetId": "markdown",
485 | "widgetNamespace": "builtin"
486 | },
487 | {
488 | "fieldId": "publishedAt",
489 | "settings": {
490 | "ampm": "24",
491 | "format": "timeZ"
492 | },
493 | "widgetId": "datePicker",
494 | "widgetNamespace": "builtin"
495 | },
496 | {
497 | "fieldId": "author",
498 | "widgetId": "entryLinkEditor",
499 | "widgetNamespace": "builtin"
500 | },
501 | {
502 | "fieldId": "category",
503 | "widgetId": "entryLinkEditor",
504 | "widgetNamespace": "builtin"
505 | }
506 | ]
507 | }
508 | ],
509 | "entries": [
510 | {
511 | "sys": {
512 | "space": {
513 | "sys": {
514 | "type": "Link",
515 | "linkType": "Space",
516 | "id": "el2orhksvy4m"
517 | }
518 | },
519 | "id": "4MUpPfHcL6acD902cJSScK",
520 | "type": "Entry",
521 | "createdAt": "2020-04-19T21:30:28.769Z",
522 | "updatedAt": "2020-04-19T21:30:57.596Z",
523 | "environment": {
524 | "sys": {
525 | "id": "master",
526 | "type": "Link",
527 | "linkType": "Environment"
528 | }
529 | },
530 | "publishedVersion": 7,
531 | "publishedAt": "2020-04-19T21:30:57.596Z",
532 | "firstPublishedAt": "2020-04-19T21:30:57.596Z",
533 | "createdBy": {
534 | "sys": {
535 | "type": "Link",
536 | "linkType": "User",
537 | "id": "2b9r06bKrCs1SX7eGWjrer"
538 | }
539 | },
540 | "updatedBy": {
541 | "sys": {
542 | "type": "Link",
543 | "linkType": "User",
544 | "id": "2b9r06bKrCs1SX7eGWjrer"
545 | }
546 | },
547 | "publishedCounter": 1,
548 | "version": 8,
549 | "publishedBy": {
550 | "sys": {
551 | "type": "Link",
552 | "linkType": "User",
553 | "id": "2b9r06bKrCs1SX7eGWjrer"
554 | }
555 | },
556 | "contentType": {
557 | "sys": {
558 | "type": "Link",
559 | "linkType": "ContentType",
560 | "id": "category"
561 | }
562 | }
563 | },
564 | "fields": {
565 | "name": {
566 | "en-US": "General"
567 | },
568 | "slug": {
569 | "en-US": "general"
570 | }
571 | }
572 | },
573 | {
574 | "sys": {
575 | "space": {
576 | "sys": {
577 | "type": "Link",
578 | "linkType": "Space",
579 | "id": "el2orhksvy4m"
580 | }
581 | },
582 | "id": "KI6XqoKVPYqWId0lZzNFh",
583 | "type": "Entry",
584 | "createdAt": "2020-04-19T21:31:30.875Z",
585 | "updatedAt": "2020-04-19T21:31:40.551Z",
586 | "environment": {
587 | "sys": {
588 | "id": "master",
589 | "type": "Link",
590 | "linkType": "Environment"
591 | }
592 | },
593 | "publishedVersion": 7,
594 | "publishedAt": "2020-04-19T21:31:40.551Z",
595 | "firstPublishedAt": "2020-04-19T21:31:40.551Z",
596 | "createdBy": {
597 | "sys": {
598 | "type": "Link",
599 | "linkType": "User",
600 | "id": "2b9r06bKrCs1SX7eGWjrer"
601 | }
602 | },
603 | "updatedBy": {
604 | "sys": {
605 | "type": "Link",
606 | "linkType": "User",
607 | "id": "2b9r06bKrCs1SX7eGWjrer"
608 | }
609 | },
610 | "publishedCounter": 1,
611 | "version": 8,
612 | "publishedBy": {
613 | "sys": {
614 | "type": "Link",
615 | "linkType": "User",
616 | "id": "2b9r06bKrCs1SX7eGWjrer"
617 | }
618 | },
619 | "contentType": {
620 | "sys": {
621 | "type": "Link",
622 | "linkType": "ContentType",
623 | "id": "author"
624 | }
625 | }
626 | },
627 | "fields": {
628 | "name": {
629 | "en-US": "Maxi Gimenez"
630 | },
631 | "photo": {
632 | "en-US": {
633 | "sys": {
634 | "type": "Link",
635 | "linkType": "Asset",
636 | "id": "2xts3o136ESaFyjcRkNjIV"
637 | }
638 | }
639 | }
640 | }
641 | },
642 | {
643 | "sys": {
644 | "space": {
645 | "sys": {
646 | "type": "Link",
647 | "linkType": "Space",
648 | "id": "el2orhksvy4m"
649 | }
650 | },
651 | "id": "5Bk4Zi2qQJKocz5j2iSFzS",
652 | "type": "Entry",
653 | "createdAt": "2020-04-19T21:33:14.300Z",
654 | "updatedAt": "2020-04-19T21:33:49.891Z",
655 | "environment": {
656 | "sys": {
657 | "id": "master",
658 | "type": "Link",
659 | "linkType": "Environment"
660 | }
661 | },
662 | "publishedVersion": 10,
663 | "publishedAt": "2020-04-19T21:33:49.891Z",
664 | "firstPublishedAt": "2020-04-19T21:33:49.891Z",
665 | "createdBy": {
666 | "sys": {
667 | "type": "Link",
668 | "linkType": "User",
669 | "id": "2b9r06bKrCs1SX7eGWjrer"
670 | }
671 | },
672 | "updatedBy": {
673 | "sys": {
674 | "type": "Link",
675 | "linkType": "User",
676 | "id": "2b9r06bKrCs1SX7eGWjrer"
677 | }
678 | },
679 | "publishedCounter": 1,
680 | "version": 11,
681 | "publishedBy": {
682 | "sys": {
683 | "type": "Link",
684 | "linkType": "User",
685 | "id": "2b9r06bKrCs1SX7eGWjrer"
686 | }
687 | },
688 | "contentType": {
689 | "sys": {
690 | "type": "Link",
691 | "linkType": "ContentType",
692 | "id": "post"
693 | }
694 | }
695 | },
696 | "fields": {
697 | "title": {
698 | "en-US": "Other cool post"
699 | },
700 | "slug": {
701 | "en-US": "other-cool-post"
702 | },
703 | "hero": {
704 | "en-US": {
705 | "sys": {
706 | "type": "Link",
707 | "linkType": "Asset",
708 | "id": "5iUDnHbf75bbBUNj9YS0zh"
709 | }
710 | }
711 | },
712 | "shortBody": {
713 | "en-US": "__Short description__ for the new blog post, to try how it looks with less content."
714 | },
715 | "body": {
716 | "en-US": "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of __Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.__\n\nLorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to *make a type specimen book. It has survived not only five centuries*, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.\n\n\n\nLorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."
717 | },
718 | "publishedAt": {
719 | "en-US": "2020-04-02T00:00+02:00"
720 | },
721 | "author": {
722 | "en-US": {
723 | "sys": {
724 | "type": "Link",
725 | "linkType": "Entry",
726 | "id": "KI6XqoKVPYqWId0lZzNFh"
727 | }
728 | }
729 | },
730 | "category": {
731 | "en-US": {
732 | "sys": {
733 | "type": "Link",
734 | "linkType": "Entry",
735 | "id": "4MUpPfHcL6acD902cJSScK"
736 | }
737 | }
738 | }
739 | }
740 | },
741 | {
742 | "sys": {
743 | "space": {
744 | "sys": {
745 | "type": "Link",
746 | "linkType": "Space",
747 | "id": "el2orhksvy4m"
748 | }
749 | },
750 | "id": "35QlNLR7b0k2SUNtX19fYO",
751 | "type": "Entry",
752 | "createdAt": "2020-04-19T21:34:11.388Z",
753 | "updatedAt": "2020-04-19T21:34:39.333Z",
754 | "environment": {
755 | "sys": {
756 | "id": "master",
757 | "type": "Link",
758 | "linkType": "Environment"
759 | }
760 | },
761 | "publishedVersion": 11,
762 | "publishedAt": "2020-04-19T21:34:39.333Z",
763 | "firstPublishedAt": "2020-04-19T21:34:39.333Z",
764 | "createdBy": {
765 | "sys": {
766 | "type": "Link",
767 | "linkType": "User",
768 | "id": "2b9r06bKrCs1SX7eGWjrer"
769 | }
770 | },
771 | "updatedBy": {
772 | "sys": {
773 | "type": "Link",
774 | "linkType": "User",
775 | "id": "2b9r06bKrCs1SX7eGWjrer"
776 | }
777 | },
778 | "publishedCounter": 1,
779 | "version": 12,
780 | "publishedBy": {
781 | "sys": {
782 | "type": "Link",
783 | "linkType": "User",
784 | "id": "2b9r06bKrCs1SX7eGWjrer"
785 | }
786 | },
787 | "contentType": {
788 | "sys": {
789 | "type": "Link",
790 | "linkType": "ContentType",
791 | "id": "post"
792 | }
793 | }
794 | },
795 | "fields": {
796 | "title": {
797 | "en-US": "Next.js + Contentful + Medium style blog"
798 | },
799 | "slug": {
800 | "en-US": "nextjs-contentful-medium"
801 | },
802 | "hero": {
803 | "en-US": {
804 | "sys": {
805 | "type": "Link",
806 | "linkType": "Asset",
807 | "id": "1nf8ONgIhGEu1Ybo1d4PnH"
808 | }
809 | }
810 | },
811 | "shortBody": {
812 | "en-US": "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book."
813 | },
814 | "body": {
815 | "en-US": "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.\n\n> Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.\n\nLorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."
816 | },
817 | "publishedAt": {
818 | "en-US": "2020-04-19T00:00+02:00"
819 | },
820 | "author": {
821 | "en-US": {
822 | "sys": {
823 | "type": "Link",
824 | "linkType": "Entry",
825 | "id": "KI6XqoKVPYqWId0lZzNFh"
826 | }
827 | }
828 | },
829 | "category": {
830 | "en-US": {
831 | "sys": {
832 | "type": "Link",
833 | "linkType": "Entry",
834 | "id": "4MUpPfHcL6acD902cJSScK"
835 | }
836 | }
837 | }
838 | }
839 | }
840 | ],
841 | "assets": [
842 | {
843 | "sys": {
844 | "space": {
845 | "sys": {
846 | "type": "Link",
847 | "linkType": "Space",
848 | "id": "el2orhksvy4m"
849 | }
850 | },
851 | "id": "2xts3o136ESaFyjcRkNjIV",
852 | "type": "Asset",
853 | "createdAt": "2020-04-19T21:24:18.621Z",
854 | "updatedAt": "2020-04-19T21:24:28.716Z",
855 | "environment": {
856 | "sys": {
857 | "id": "master",
858 | "type": "Link",
859 | "linkType": "Environment"
860 | }
861 | },
862 | "publishedVersion": 2,
863 | "publishedAt": "2020-04-19T21:24:28.716Z",
864 | "firstPublishedAt": "2020-04-19T21:24:28.716Z",
865 | "createdBy": {
866 | "sys": {
867 | "type": "Link",
868 | "linkType": "User",
869 | "id": "2b9r06bKrCs1SX7eGWjrer"
870 | }
871 | },
872 | "updatedBy": {
873 | "sys": {
874 | "type": "Link",
875 | "linkType": "User",
876 | "id": "2b9r06bKrCs1SX7eGWjrer"
877 | }
878 | },
879 | "publishedCounter": 1,
880 | "version": 3,
881 | "publishedBy": {
882 | "sys": {
883 | "type": "Link",
884 | "linkType": "User",
885 | "id": "2b9r06bKrCs1SX7eGWjrer"
886 | }
887 | }
888 | },
889 | "fields": {
890 | "title": {
891 | "en-US": "Maxi Gimenez"
892 | },
893 | "file": {
894 | "en-US": {
895 | "url": "//images.ctfassets.net/el2orhksvy4m/2xts3o136ESaFyjcRkNjIV/10ee6cbee2257d9b1a28f8bc7690026c/IMG_2404-min.jpg",
896 | "details": {
897 | "size": 500396,
898 | "image": {
899 | "width": 2283,
900 | "height": 2583
901 | }
902 | },
903 | "fileName": "IMG_2404-min.jpg",
904 | "contentType": "image/jpeg"
905 | }
906 | }
907 | }
908 | },
909 | {
910 | "sys": {
911 | "space": {
912 | "sys": {
913 | "type": "Link",
914 | "linkType": "Space",
915 | "id": "el2orhksvy4m"
916 | }
917 | },
918 | "id": "1nf8ONgIhGEu1Ybo1d4PnH",
919 | "type": "Asset",
920 | "createdAt": "2020-04-19T21:24:18.825Z",
921 | "updatedAt": "2020-04-19T21:24:29.143Z",
922 | "environment": {
923 | "sys": {
924 | "id": "master",
925 | "type": "Link",
926 | "linkType": "Environment"
927 | }
928 | },
929 | "publishedVersion": 2,
930 | "publishedAt": "2020-04-19T21:24:29.143Z",
931 | "firstPublishedAt": "2020-04-19T21:24:29.143Z",
932 | "createdBy": {
933 | "sys": {
934 | "type": "Link",
935 | "linkType": "User",
936 | "id": "2b9r06bKrCs1SX7eGWjrer"
937 | }
938 | },
939 | "updatedBy": {
940 | "sys": {
941 | "type": "Link",
942 | "linkType": "User",
943 | "id": "2b9r06bKrCs1SX7eGWjrer"
944 | }
945 | },
946 | "publishedCounter": 1,
947 | "version": 3,
948 | "publishedBy": {
949 | "sys": {
950 | "type": "Link",
951 | "linkType": "User",
952 | "id": "2b9r06bKrCs1SX7eGWjrer"
953 | }
954 | }
955 | },
956 | "fields": {
957 | "title": {
958 | "en-US": "Moving"
959 | },
960 | "description": {
961 | "en-US": "Photo by Marc-Olivier Jodoin on Unsplash"
962 | },
963 | "file": {
964 | "en-US": {
965 | "url": "//images.ctfassets.net/el2orhksvy4m/1nf8ONgIhGEu1Ybo1d4PnH/90e8c1f4f4e0ddb3ec10323715d49548/marc-olivier-jodoin-NqOInJ-ttqM-unsplash__1_.jpg",
966 | "details": {
967 | "size": 252046,
968 | "image": {
969 | "width": 2000,
970 | "height": 1125
971 | }
972 | },
973 | "fileName": "marc-olivier-jodoin-NqOInJ-ttqM-unsplash (1).jpg",
974 | "contentType": "image/jpeg"
975 | }
976 | }
977 | }
978 | },
979 | {
980 | "sys": {
981 | "space": {
982 | "sys": {
983 | "type": "Link",
984 | "linkType": "Space",
985 | "id": "el2orhksvy4m"
986 | }
987 | },
988 | "id": "5iUDnHbf75bbBUNj9YS0zh",
989 | "type": "Asset",
990 | "createdAt": "2020-04-19T21:24:19.106Z",
991 | "updatedAt": "2020-04-19T21:24:28.172Z",
992 | "environment": {
993 | "sys": {
994 | "id": "master",
995 | "type": "Link",
996 | "linkType": "Environment"
997 | }
998 | },
999 | "publishedVersion": 2,
1000 | "publishedAt": "2020-04-19T21:24:28.172Z",
1001 | "firstPublishedAt": "2020-04-19T21:24:28.172Z",
1002 | "createdBy": {
1003 | "sys": {
1004 | "type": "Link",
1005 | "linkType": "User",
1006 | "id": "2b9r06bKrCs1SX7eGWjrer"
1007 | }
1008 | },
1009 | "updatedBy": {
1010 | "sys": {
1011 | "type": "Link",
1012 | "linkType": "User",
1013 | "id": "2b9r06bKrCs1SX7eGWjrer"
1014 | }
1015 | },
1016 | "publishedCounter": 1,
1017 | "version": 3,
1018 | "publishedBy": {
1019 | "sys": {
1020 | "type": "Link",
1021 | "linkType": "User",
1022 | "id": "2b9r06bKrCs1SX7eGWjrer"
1023 | }
1024 | }
1025 | },
1026 | "fields": {
1027 | "title": {
1028 | "en-US": "photo-1506775352297-a5fa9c136675"
1029 | },
1030 | "file": {
1031 | "en-US": {
1032 | "url": "//images.ctfassets.net/el2orhksvy4m/5iUDnHbf75bbBUNj9YS0zh/49006819d437fedf1a840253571ab4c8/photo-1506775352297-a5fa9c136675",
1033 | "details": {
1034 | "size": 113088,
1035 | "image": {
1036 | "width": 1489,
1037 | "height": 838
1038 | }
1039 | },
1040 | "fileName": "photo-1506775352297-a5fa9c136675",
1041 | "contentType": "image/jpeg"
1042 | }
1043 | }
1044 | }
1045 | }
1046 | ],
1047 | "locales": [
1048 | {
1049 | "name": "English (United States)",
1050 | "code": "en-US",
1051 | "fallbackCode": null,
1052 | "default": true,
1053 | "contentManagementApi": true,
1054 | "contentDeliveryApi": true,
1055 | "optional": false,
1056 | "sys": {
1057 | "type": "Locale",
1058 | "id": "5FjdkmToYwbWvINfjT0W6w",
1059 | "version": 1,
1060 | "space": {
1061 | "sys": {
1062 | "type": "Link",
1063 | "linkType": "Space",
1064 | "id": "el2orhksvy4m"
1065 | }
1066 | },
1067 | "environment": {
1068 | "sys": {
1069 | "type": "Link",
1070 | "linkType": "Environment",
1071 | "id": "master"
1072 | }
1073 | },
1074 | "createdBy": {
1075 | "sys": {
1076 | "type": "Link",
1077 | "linkType": "User",
1078 | "id": "2b9r06bKrCs1SX7eGWjrer"
1079 | }
1080 | },
1081 | "createdAt": "2020-04-19T20:53:21Z",
1082 | "updatedBy": {
1083 | "sys": {
1084 | "type": "Link",
1085 | "linkType": "User",
1086 | "id": "2b9r06bKrCs1SX7eGWjrer"
1087 | }
1088 | },
1089 | "updatedAt": "2020-04-19T20:53:21Z"
1090 | }
1091 | }
1092 | ],
1093 | "webhooks": [
1094 | ],
1095 | "roles": [
1096 | ]
1097 | }
1098 |
--------------------------------------------------------------------------------
/setup-contentful.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maxigimenez/next-medium-blog-boilerplate/b39bdc54932b9fdcc8803ead2440edf280501121/setup-contentful.png
--------------------------------------------------------------------------------
/styles/bootstrap-user-variables.scss:
--------------------------------------------------------------------------------
1 |
2 | // --------------------------------------- !!
3 | // Overwrite specific bootstrap variables by uncommenting the variable and provide your own value
4 | // --------------------------------------- !!
5 |
6 | // Variables
7 | //
8 | // Variables should follow the `$component-state-property-size` formula for
9 | // consistent naming. Ex: $nav-link-disabled-color and $modal-content-box-shadow-xs.
10 |
11 |
12 | //
13 | // Color system
14 | //
15 |
16 | $white: #fff !default;
17 | $gray-100: #f8f9fa !default;
18 | $gray-200: #e9ecef !default;
19 | $gray-300: #dee2e6 !default;
20 | $gray-400: #ced4da !default;
21 | $gray-500: #adb5bd !default;
22 | $gray-600: #6c757d !default;
23 | $gray-700: #495057 !default;
24 | $gray-800: #343a40 !default;
25 | $gray-900: #212529 !default;
26 | $black: #000 !default;
27 |
28 |
29 | $grays: () !default;
30 | // // stylelint-disable-next-line scss/dollar-variable-default
31 | $grays: map-merge(
32 | (
33 | "100": $gray-100,
34 | "200": $gray-200,
35 | "300": $gray-300,
36 | "400": $gray-400,
37 | "500": $gray-500,
38 | "600": $gray-600,
39 | "700": $gray-700,
40 | "800": $gray-800,
41 | "900": $gray-900
42 | ),
43 | $grays
44 | );
45 |
46 |
47 | $blue: #7832e2 !default;
48 | $indigo: #502c6c !default;
49 | $purple: #ad6edd !default;
50 | $pink: #ff0266 !default;
51 | $red: #ea2f65 !default;
52 | $orange: #fbb500 !default;
53 | $yellow: #ffde03 !default;
54 | $green: #03a87c !default;
55 | $teal: #09ebaf !default;
56 | $cyan: #35bdff !default;
57 | $lila: #b2a5e4 !default;
58 | $salmon: #ff977a !default;
59 | $lightblue: #e8f3ec !default;
60 |
61 |
62 | $colors: () !default;
63 | // // stylelint-disable-next-line scss/dollar-variable-default
64 | $colors: map-merge(
65 | (
66 | "blue": $blue,
67 | "indigo": $indigo,
68 | "purple": $purple,
69 | "pink": $pink,
70 | "red": $red,
71 | "orange": $orange,
72 | "yellow": $yellow,
73 | "green": $green,
74 | "teal": $teal,
75 | "cyan": $cyan,
76 | "white": $white,
77 | "gray": $gray-600,
78 | "gray-dark": $gray-800,
79 | "light-blue": $lightblue
80 | ),
81 | $colors
82 | );
83 |
84 | $primary: $green !default;
85 | $secondary: $blue !default;
86 | $success: $green !default;
87 | $info: $teal !default;
88 | $warning: $yellow !default;
89 | $danger: $red !default;
90 | $light: $gray-100 !default;
91 | $dark: $gray-900 !default;
92 | $white: $white !default;
93 | $lightblue: $lightblue !default;
94 |
95 |
96 | $theme-colors: () !default;
97 | // // stylelint-disable-next-line scss/dollar-variable-default
98 | $theme-colors: map-merge(
99 | (
100 | "primary": $primary,
101 | "secondary": $secondary,
102 | "success": $success,
103 | "info": $info,
104 | "warning": $warning,
105 | "danger": $danger,
106 | "light": $light,
107 | "dark": $dark,
108 | "white": $white,
109 | "purple": $purple,
110 | "salmon": $salmon,
111 | "cyan": $cyan,
112 | "gray": $gray-400,
113 | "indigo": $indigo,
114 | "orange": $orange,
115 | "lightblue": $lightblue
116 | ),
117 | $theme-colors
118 | );
119 | //
120 | // // Set a specific jump point for requesting color jumps
121 | // $theme-color-interval: 8% !default;
122 | //
123 | // // The yiq lightness value that determines when the lightness of color changes from "dark" to "light". Acceptable values are between 0 and 255.
124 | // $yiq-contrasted-threshold: 150 !default;
125 | //
126 | // // Customize the light and dark text colors for use in our YIQ color contrast function.
127 | // $yiq-text-dark: $gray-900 !default;
128 | // $yiq-text-light: $white !default;
129 | //
130 | // // Options
131 | // //
132 | // // Quickly modify global styling by enabling or disabling optional features.
133 | //
134 | // $enable-caret: true !default;
135 | // $enable-rounded: true !default;
136 | // $enable-shadows: false !default;
137 | // $enable-gradients: false !default;
138 | // $enable-transitions: true !default;
139 | // $enable-hover-media-query: false !default; // Deprecated, no longer affects any compiled CSS
140 | // $enable-grid-classes: true !default;
141 | // $enable-print-styles: true !default;
142 | //
143 | //
144 | // // Spacing
145 | // //
146 | // // Control the default styling of most Bootstrap elements by modifying these
147 | // // variables. Mostly focused on spacing.
148 | // // You can add more entries to the $spacers map, should you need more variation.
149 | //
150 | $spacer: 1rem !default;
151 | $spacers: () !default;
152 | // // stylelint-disable-next-line scss/dollar-variable-default
153 | $spacers: map-merge(
154 | (
155 | 0: 0,
156 | 1: ($spacer * .25),
157 | 2: ($spacer * .5),
158 | 3: $spacer,
159 | 4: ($spacer * 1.5),
160 | 5: ($spacer * 3),
161 | 6: ($spacer * 5),
162 | ),
163 | $spacers
164 | );
165 | //
166 | // // This variable affects the `.h-*` and `.w-*` classes.
167 | // $sizes: () !default;
168 | // // stylelint-disable-next-line scss/dollar-variable-default
169 | // $sizes: map-merge(
170 | // (
171 | // 25: 25%,
172 | // 50: 50%,
173 | // 75: 75%,
174 | // 100: 100%,
175 | // auto: auto
176 | // ),
177 | // $sizes
178 | // );
179 | //
180 | // // Body
181 | // //
182 | // // Settings for the `` element.
183 | //
184 | // $body-bg: $white !default;
185 | // $body-color: $gray-900 !default;
186 | //
187 | // // Links
188 | // //
189 | // // Style anchor elements.
190 | //
191 | // $link-color: theme-color("primary") !default;
192 | // $link-decoration: none !default;
193 | // $link-hover-color: darken($link-color, 15%) !default;
194 | // $link-hover-decoration: underline !default;
195 | //
196 | // // Paragraphs
197 | // //
198 | // // Style p element.
199 | //
200 | // $paragraph-margin-bottom: 1rem !default;
201 | //
202 | //
203 | // // Grid breakpoints
204 | // //
205 | // // Define the minimum dimensions at which your layout will change,
206 | // // adapting to different screen sizes, for use in media queries.
207 | //
208 | // $grid-breakpoints: (
209 | // xs: 0,
210 | // sm: 576px,
211 | // md: 768px,
212 | // lg: 992px,
213 | // xl: 1200px
214 | // ) !default;
215 | //
216 | // @include _assert-ascending($grid-breakpoints, "$grid-breakpoints");
217 | // @include _assert-starts-at-zero($grid-breakpoints);
218 | //
219 | //
220 | // // Grid containers
221 | // //
222 | // // Define the maximum width of `.container` for different screen sizes.
223 | //
224 | // $container-max-widths: (
225 | // sm: 540px,
226 | // md: 720px,
227 | // lg: 960px,
228 | // xl: 1140px
229 | // ) !default;
230 | //
231 | // @include _assert-ascending($container-max-widths, "$container-max-widths");
232 | //
233 | //
234 | // // Grid columns
235 | // //
236 | // // Set the number of columns and specify the width of the gutters.
237 | //
238 | // $grid-columns: 12 !default;
239 | // $grid-gutter-width: 30px !default;
240 | //
241 | // // Components
242 | // //
243 | // // Define common padding and border radius sizes and more.
244 | //
245 | // $line-height-lg: 1.5 !default;
246 | // $line-height-sm: 1.5 !default;
247 | //
248 | // $border-width: 1px !default;
249 | $border-color: $lightblue !default;
250 | //
251 | // $border-radius: .25rem !default;
252 | // $border-radius-lg: .3rem !default;
253 | // $border-radius-sm: .2rem !default;
254 | //
255 | // $box-shadow-sm: 0 .125rem .25rem rgba($black, .075) !default;
256 | // $box-shadow: 0 .5rem 1rem rgba($black, .15) !default;
257 | $box-shadow-lg: 0 10px 25px 0 rgba($black,.3) !default;
258 | //
259 | // $component-active-color: $white !default;
260 | // $component-active-bg: theme-color("primary") !default;
261 | //
262 | // $caret-width: .3em !default;
263 | //
264 | // $transition-base: all .2s ease-in-out !default;
265 | // $transition-fade: opacity .15s linear !default;
266 | // $transition-collapse: height .35s ease !default;
267 | //
268 | //
269 | // // Fonts
270 | // //
271 | // // Font, line-height, and color for body text, headings, and more.
272 | //
273 | // // stylelint-disable value-keyword-case
274 | $font-family-sans-serif: "Source Sans Pro", "Segoe UI", "Helvetica Neue", "Arial" !default;
275 | $font-secondary: "Playfair Display", "Segoe UI", "Helvetica Neue", "Arial" !default;
276 | // $font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace !default;
277 | // $font-family-base: $font-family-sans-serif !default;
278 | // // stylelint-enable value-keyword-case
279 | //
280 | $font-size-base: 1rem !default; // Assumes the browser default, typically `16px`
281 | $font-size-lg: ($font-size-base * 1.15) !default;
282 | $font-size-smaller: ($font-size-base * 1) !default;
283 | // $font-size-sm: ($font-size-base * .875) !default;
284 | //
285 | $font-weight-light: 300 !default;
286 | $font-weight-normal: 400 !default;
287 | // $font-weight-bold: 700 !default;
288 | //
289 | $font-weight-base: $font-weight-normal !default;
290 | $line-height-base: 1.6 !default;
291 | //
292 | // $h1-font-size: $font-size-base * 2.5 !default;
293 | // $h2-font-size: $font-size-base * 2 !default;
294 | // $h3-font-size: $font-size-base * 1.75 !default;
295 | // $h4-font-size: $font-size-base * 1.5 !default;
296 | // $h5-font-size: $font-size-base * 1.25 !default;
297 | // $h6-font-size: $font-size-base !default;
298 | //
299 | // $headings-margin-bottom: ($spacer / 2) !default;
300 | $headings-font-family: inherit !default;
301 | $headings-font-weight: 500 !default;
302 | $headings-line-height: 1.2 !default;
303 | // $headings-color: inherit !default;
304 | //
305 | // $display1-size: 6rem !default;
306 | // $display2-size: 5.5rem !default;
307 | // $display3-size: 4.5rem !default;
308 | // $display4-size: 3.5rem !default;
309 | //
310 | // $display1-weight: 300 !default;
311 | // $display2-weight: 300 !default;
312 | // $display3-weight: 300 !default;
313 | // $display4-weight: 300 !default;
314 | // $display-line-height: $headings-line-height !default;
315 | //
316 | // $lead-font-size: ($font-size-base * 1.25) !default;
317 | // $lead-font-weight: 300 !default;
318 | //
319 | $small-font-size: 85% !default;
320 | //
321 | // $text-muted: $gray-600 !default;
322 | //
323 | // $blockquote-small-color: $gray-600 !default;
324 | // $blockquote-font-size: ($font-size-base * 1.25) !default;
325 | //
326 | // $hr-border-color: rgba($black, .1) !default;
327 | // $hr-border-width: $border-width !default;
328 | //
329 | // $mark-padding: .2em !default;
330 | //
331 | // $dt-font-weight: $font-weight-bold !default;
332 | //
333 | // $kbd-box-shadow: inset 0 -.1rem 0 rgba($black, .25) !default;
334 | // $nested-kbd-font-weight: $font-weight-bold !default;
335 | //
336 | // $list-inline-padding: .5rem !default;
337 | //
338 | // $mark-bg: #fcf8e3 !default;
339 | //
340 | // $hr-margin-y: $spacer !default;
341 | //
342 | //
343 | // // Tables
344 | // //
345 | // // Customizes the `.table` component with basic values, each used across all table variations.
346 | //
347 | // $table-cell-padding: .75rem !default;
348 | // $table-cell-padding-sm: .3rem !default;
349 | //
350 | // $table-bg: transparent !default;
351 | // $table-accent-bg: rgba($black, .05) !default;
352 | // $table-hover-bg: rgba($black, .075) !default;
353 | // $table-active-bg: $table-hover-bg !default;
354 | //
355 | // $table-border-width: $border-width !default;
356 | // $table-border-color: $gray-300 !default;
357 | //
358 | // $table-head-bg: $gray-200 !default;
359 | // $table-head-color: $gray-700 !default;
360 | //
361 | // $table-dark-bg: $gray-900 !default;
362 | // $table-dark-accent-bg: rgba($white, .05) !default;
363 | // $table-dark-hover-bg: rgba($white, .075) !default;
364 | // $table-dark-border-color: lighten($gray-900, 7.5%) !default;
365 | // $table-dark-color: $body-bg !default;
366 | //
367 | // $table-striped-order: odd !default;
368 | //
369 | // $table-caption-color: $text-muted !default;
370 | //
371 | // // Buttons + Forms
372 | // //
373 | // // Shared variables that are reassigned to `$input-` and `$btn-` specific variables.
374 | //
375 | $input-btn-padding-y: .45rem !default;
376 | $input-btn-padding-x: 1.2rem !default;
377 | // $input-btn-line-height: $line-height-base !default;
378 | //
379 | // $input-btn-focus-width: .2rem !default;
380 | // $input-btn-focus-color: rgba($component-active-bg, .25) !default;
381 | // $input-btn-focus-box-shadow: 0 0 0 $input-btn-focus-width $input-btn-focus-color !default;
382 | //
383 | // $input-btn-padding-y-sm: .25rem !default;
384 | $input-btn-padding-x-sm: 1rem !default;
385 | // $input-btn-line-height-sm: $line-height-sm !default;
386 | //
387 | $input-btn-padding-y-lg: .65rem !default;
388 | $input-btn-padding-x-lg: 2rem !default;
389 | // $input-btn-line-height-lg: $line-height-lg !default;
390 | //
391 | // $input-btn-border-width: $border-width !default;
392 | //
393 | //
394 | // // Buttons
395 | // //
396 | // // For each of Bootstrap's buttons, define text, background, and border color.
397 | //
398 | // $btn-padding-y: $input-btn-padding-y !default;
399 | // $btn-padding-x: $input-btn-padding-x !default;
400 | // $btn-line-height: $input-btn-line-height !default;
401 | //
402 | // $btn-padding-y-sm: $input-btn-padding-y-sm !default;
403 | // $btn-padding-x-sm: $input-btn-padding-x-sm !default;
404 | // $btn-line-height-sm: $input-btn-line-height-sm !default;
405 | //
406 | // $btn-padding-y-lg: $input-btn-padding-y-lg !default;
407 | // $btn-padding-x-lg: $input-btn-padding-x-lg !default;
408 | // $btn-line-height-lg: $input-btn-line-height-lg !default;
409 | //
410 | // $btn-border-width: $input-btn-border-width !default;
411 | //
412 | // $btn-font-weight: $font-weight-normal !default;
413 | // $btn-box-shadow: inset 0 1px 0 rgba($white, .15), 0 1px 1px rgba($black, .075) !default;
414 | // $btn-focus-width: $input-btn-focus-width !default;
415 | // $btn-focus-box-shadow: $input-btn-focus-box-shadow !default;
416 | // $btn-disabled-opacity: .65 !default;
417 | // $btn-active-box-shadow: inset 0 3px 5px rgba($black, .125) !default;
418 | //
419 | // $btn-link-disabled-color: $gray-600 !default;
420 | //
421 | // $btn-block-spacing-y: .5rem !default;
422 | //
423 | // // Allows for customizing button radius independently from global border radius
424 | // $btn-border-radius: $border-radius !default;
425 | // $btn-border-radius-lg: $border-radius-lg !default;
426 | // $btn-border-radius-sm: $border-radius-sm !default;
427 | //
428 | // $btn-transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out !default;
429 |
430 | //
431 | //
432 | // // Forms
433 | //
434 | // $label-margin-bottom: .5rem !default;
435 | //
436 | // $input-padding-y: $input-btn-padding-y !default;
437 | // $input-padding-x: $input-btn-padding-x !default;
438 | // $input-line-height: $input-btn-line-height !default;
439 | //
440 | // $input-padding-y-sm: $input-btn-padding-y-sm !default;
441 | // $input-padding-x-sm: $input-btn-padding-x-sm !default;
442 | // $input-line-height-sm: $input-btn-line-height-sm !default;
443 | //
444 | // $input-padding-y-lg: $input-btn-padding-y-lg !default;
445 | // $input-padding-x-lg: $input-btn-padding-x-lg !default;
446 | // $input-line-height-lg: $input-btn-line-height-lg !default;
447 | //
448 | // $input-bg: $white !default;
449 | // $input-disabled-bg: $gray-200 !default;
450 | //
451 | // $input-color: $gray-700 !default;
452 | // $input-border-color: $gray-400 !default;
453 | // $input-border-width: $input-btn-border-width !default;
454 | // $input-box-shadow: inset 0 1px 1px rgba($black, .075) !default;
455 | //
456 | // $input-border-radius: $border-radius !default;
457 | // $input-border-radius-lg: $border-radius-lg !default;
458 | // $input-border-radius-sm: $border-radius-sm !default;
459 | //
460 | // $input-focus-bg: $input-bg !default;
461 | // $input-focus-border-color: lighten($component-active-bg, 25%) !default;
462 | // $input-focus-color: $input-color !default;
463 | // $input-focus-width: $input-btn-focus-width !default;
464 | // $input-focus-box-shadow: $input-btn-focus-box-shadow !default;
465 | //
466 | // $input-placeholder-color: $gray-600 !default;
467 | // $input-plaintext-color: $body-color !default;
468 | //
469 | // $input-height-border: $input-border-width * 2 !default;
470 | //
471 | // $input-height-inner: ($font-size-base * $input-btn-line-height) + ($input-btn-padding-y * 2) !default;
472 | // $input-height: calc(#{$input-height-inner} + #{$input-height-border}) !default;
473 | //
474 | // $input-height-inner-sm: ($font-size-sm * $input-btn-line-height-sm) + ($input-btn-padding-y-sm * 2) !default;
475 | // $input-height-sm: calc(#{$input-height-inner-sm} + #{$input-height-border}) !default;
476 | //
477 | // $input-height-inner-lg: ($font-size-lg * $input-btn-line-height-lg) + ($input-btn-padding-y-lg * 2) !default;
478 | // $input-height-lg: calc(#{$input-height-inner-lg} + #{$input-height-border}) !default;
479 | //
480 | // $input-transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out !default;
481 | //
482 | // $form-text-margin-top: .25rem !default;
483 | //
484 | // $form-check-input-gutter: 1.25rem !default;
485 | // $form-check-input-margin-y: .3rem !default;
486 | // $form-check-input-margin-x: .25rem !default;
487 | //
488 | // $form-check-inline-margin-x: .75rem !default;
489 | // $form-check-inline-input-margin-x: .3125rem !default;
490 | //
491 | // $form-group-margin-bottom: 1rem !default;
492 | //
493 | // $input-group-addon-color: $input-color !default;
494 | // $input-group-addon-bg: $gray-200 !default;
495 | // $input-group-addon-border-color: $input-border-color !default;
496 | //
497 | // $custom-forms-transition: background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out !default;
498 | //
499 | // $custom-control-gutter: 1.5rem !default;
500 | // $custom-control-spacer-x: 1rem !default;
501 | //
502 | // $custom-control-indicator-size: 1rem !default;
503 | // $custom-control-indicator-bg: $gray-300 !default;
504 | // $custom-control-indicator-bg-size: 50% 50% !default;
505 | // $custom-control-indicator-box-shadow: inset 0 .25rem .25rem rgba($black, .1) !default;
506 | //
507 | // $custom-control-indicator-disabled-bg: $gray-200 !default;
508 | // $custom-control-label-disabled-color: $gray-600 !default;
509 | //
510 | // $custom-control-indicator-checked-color: $component-active-color !default;
511 | // $custom-control-indicator-checked-bg: $component-active-bg !default;
512 | // $custom-control-indicator-checked-disabled-bg: rgba(theme-color("primary"), .5) !default;
513 | // $custom-control-indicator-checked-box-shadow: none !default;
514 | //
515 | // $custom-control-indicator-focus-box-shadow: 0 0 0 1px $body-bg, $input-btn-focus-box-shadow !default;
516 | //
517 | // $custom-control-indicator-active-color: $component-active-color !default;
518 | // $custom-control-indicator-active-bg: lighten($component-active-bg, 35%) !default;
519 | // $custom-control-indicator-active-box-shadow: none !default;
520 | //
521 | // $custom-checkbox-indicator-border-radius: $border-radius !default;
522 | // $custom-checkbox-indicator-icon-checked: str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='#{$custom-control-indicator-checked-color}' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3E%3C/svg%3E"), "#", "%23") !default;
523 | //
524 | // $custom-checkbox-indicator-indeterminate-bg: $component-active-bg !default;
525 | // $custom-checkbox-indicator-indeterminate-color: $custom-control-indicator-checked-color !default;
526 | // $custom-checkbox-indicator-icon-indeterminate: str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3E%3Cpath stroke='#{$custom-checkbox-indicator-indeterminate-color}' d='M0 2h4'/%3E%3C/svg%3E"), "#", "%23") !default;
527 | // $custom-checkbox-indicator-indeterminate-box-shadow: none !default;
528 | //
529 | // $custom-radio-indicator-border-radius: 50% !default;
530 | // $custom-radio-indicator-icon-checked: str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3E%3Ccircle r='3' fill='#{$custom-control-indicator-checked-color}'/%3E%3C/svg%3E"), "#", "%23") !default;
531 | //
532 | // $custom-select-padding-y: .375rem !default;
533 | // $custom-select-padding-x: .75rem !default;
534 | // $custom-select-height: $input-height !default;
535 | // $custom-select-indicator-padding: 1rem !default; // Extra padding to account for the presence of the background-image based indicator
536 | // $custom-select-line-height: $input-btn-line-height !default;
537 | // $custom-select-color: $input-color !default;
538 | // $custom-select-disabled-color: $gray-600 !default;
539 | // $custom-select-bg: $input-bg !default;
540 | // $custom-select-disabled-bg: $gray-200 !default;
541 | // $custom-select-bg-size: 8px 10px !default; // In pixels because image dimensions
542 | // $custom-select-indicator-color: $gray-800 !default;
543 | // $custom-select-indicator: str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3E%3Cpath fill='#{$custom-select-indicator-color}' d='M2 0L0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E"), "#", "%23") !default;
544 | // $custom-select-border-width: $input-btn-border-width !default;
545 | // $custom-select-border-color: $input-border-color !default;
546 | // $custom-select-border-radius: $border-radius !default;
547 | // $custom-select-box-shadow: inset 0 1px 2px rgba($black, .075) !default;
548 | //
549 | // $custom-select-focus-border-color: $input-focus-border-color !default;
550 | // $custom-select-focus-width: $input-btn-focus-width !default;
551 | // $custom-select-focus-box-shadow: 0 0 0 $custom-select-focus-width rgba($custom-select-focus-border-color, .5) !default;
552 | //
553 | // $custom-select-font-size-sm: 75% !default;
554 | // $custom-select-height-sm: $input-height-sm !default;
555 | //
556 | // $custom-select-font-size-lg: 125% !default;
557 | // $custom-select-height-lg: $input-height-lg !default;
558 | //
559 | // $custom-range-track-width: 100% !default;
560 | // $custom-range-track-height: .5rem !default;
561 | // $custom-range-track-cursor: pointer !default;
562 | // $custom-range-track-bg: $gray-300 !default;
563 | // $custom-range-track-border-radius: 1rem !default;
564 | // $custom-range-track-box-shadow: inset 0 .25rem .25rem rgba($black, .1) !default;
565 | //
566 | // $custom-range-thumb-width: 1rem !default;
567 | // $custom-range-thumb-height: $custom-range-thumb-width !default;
568 | // $custom-range-thumb-bg: $component-active-bg !default;
569 | // $custom-range-thumb-border: 0 !default;
570 | // $custom-range-thumb-border-radius: 1rem !default;
571 | // $custom-range-thumb-box-shadow: 0 .1rem .25rem rgba($black, .1) !default;
572 | // $custom-range-thumb-focus-box-shadow: 0 0 0 1px $body-bg, $input-btn-focus-box-shadow !default;
573 | // $custom-range-thumb-focus-box-shadow-width: $input-btn-focus-width !default; // For focus box shadow issue in IE/Edge
574 | // $custom-range-thumb-active-bg: lighten($component-active-bg, 35%) !default;
575 | //
576 | // $custom-file-height: $input-height !default;
577 | // $custom-file-height-inner: $input-height-inner !default;
578 | // $custom-file-focus-border-color: $input-focus-border-color !default;
579 | // $custom-file-focus-box-shadow: $input-btn-focus-box-shadow !default;
580 | // $custom-file-disabled-bg: $input-disabled-bg !default;
581 | //
582 | // $custom-file-padding-y: $input-btn-padding-y !default;
583 | // $custom-file-padding-x: $input-btn-padding-x !default;
584 | // $custom-file-line-height: $input-btn-line-height !default;
585 | // $custom-file-color: $input-color !default;
586 | // $custom-file-bg: $input-bg !default;
587 | // $custom-file-border-width: $input-btn-border-width !default;
588 | // $custom-file-border-color: $input-border-color !default;
589 | // $custom-file-border-radius: $input-border-radius !default;
590 | // $custom-file-box-shadow: $input-box-shadow !default;
591 | // $custom-file-button-color: $custom-file-color !default;
592 | // $custom-file-button-bg: $input-group-addon-bg !default;
593 | // $custom-file-text: (
594 | // en: "Browse"
595 | // ) !default;
596 | //
597 | //
598 | // // Form validation
599 | // $form-feedback-margin-top: $form-text-margin-top !default;
600 | // $form-feedback-font-size: $small-font-size !default;
601 | // $form-feedback-valid-color: theme-color("success") !default;
602 | // $form-feedback-invalid-color: theme-color("danger") !default;
603 | //
604 | //
605 | // // Dropdowns
606 | // //
607 | // // Dropdown menu container and contents.
608 | //
609 | // $dropdown-min-width: 10rem !default;
610 | // $dropdown-padding-y: .5rem !default;
611 | // $dropdown-spacer: .125rem !default;
612 | // $dropdown-bg: $white !default;
613 | // $dropdown-border-color: rgba($black, .15) !default;
614 | // $dropdown-border-radius: $border-radius !default;
615 | // $dropdown-border-width: $border-width !default;
616 | // $dropdown-divider-bg: $gray-200 !default;
617 | // $dropdown-box-shadow: 0 .5rem 1rem rgba($black, .175) !default;
618 | //
619 | // $dropdown-link-color: $gray-900 !default;
620 | // $dropdown-link-hover-color: darken($gray-900, 5%) !default;
621 | // $dropdown-link-hover-bg: $gray-100 !default;
622 | //
623 | // $dropdown-link-active-color: $component-active-color !default;
624 | // $dropdown-link-active-bg: $component-active-bg !default;
625 | //
626 | // $dropdown-link-disabled-color: $gray-600 !default;
627 | //
628 | $dropdown-item-padding-y: .45rem !default;
629 | // $dropdown-item-padding-x: 1.5rem !default;
630 | //
631 | // $dropdown-header-color: $gray-600 !default;
632 | //
633 | //
634 | // // Z-index master list
635 | // //
636 | // // Warning: Avoid customizing these values. They're used for a bird's eye view
637 | // // of components dependent on the z-axis and are designed to all work together.
638 | //
639 | // $zindex-dropdown: 1000 !default;
640 | // $zindex-sticky: 1020 !default;
641 | // $zindex-fixed: 1030 !default;
642 | // $zindex-modal-backdrop: 1040 !default;
643 | // $zindex-modal: 1050 !default;
644 | // $zindex-popover: 1060 !default;
645 | // $zindex-tooltip: 1070 !default;
646 | //
647 | // // Navs
648 | //
649 | $nav-link-padding-y: .8rem !default;
650 | // $nav-link-padding-x: 1rem !default;
651 | // $nav-link-disabled-color: $gray-600 !default;
652 | //
653 | // $nav-tabs-border-color: $gray-300 !default;
654 | // $nav-tabs-border-width: $border-width !default;
655 | // $nav-tabs-border-radius: $border-radius !default;
656 | // $nav-tabs-link-hover-border-color: $gray-200 $gray-200 $nav-tabs-border-color !default;
657 | // $nav-tabs-link-active-color: $gray-700 !default;
658 | // $nav-tabs-link-active-bg: $body-bg !default;
659 | // $nav-tabs-link-active-border-color: $gray-300 $gray-300 $nav-tabs-link-active-bg !default;
660 | //
661 | // $nav-pills-border-radius: $border-radius !default;
662 | // $nav-pills-link-active-color: $component-active-color !default;
663 | // $nav-pills-link-active-bg: $component-active-bg !default;
664 | //
665 | // $nav-divider-color: $gray-200 !default;
666 | // $nav-divider-margin-y: ($spacer / 2) !default;
667 | //
668 | // // Navbar
669 | //
670 | // $navbar-padding-y: ($spacer / 2) !default;
671 | // $navbar-padding-x: $spacer !default;
672 | //
673 | $navbar-nav-link-padding-x: .9rem !default;
674 | //
675 | // $navbar-brand-font-size: $font-size-lg !default;
676 | // // Compute the navbar-brand padding-y so the navbar-brand will have the same height as navbar-text and nav-link
677 | // $nav-link-height: ($font-size-base * $line-height-base + $nav-link-padding-y * 2) !default;
678 | // $navbar-brand-height: $navbar-brand-font-size * $line-height-base !default;
679 | // $navbar-brand-padding-y: ($nav-link-height - $navbar-brand-height) / 2 !default;
680 | //
681 | // $navbar-toggler-padding-y: .25rem !default;
682 | // $navbar-toggler-padding-x: .75rem !default;
683 | // $navbar-toggler-font-size: $font-size-lg !default;
684 | // $navbar-toggler-border-radius: $btn-border-radius !default;
685 | //
686 | $navbar-dark-color: rgba($white, .85) !default;
687 | $navbar-dark-hover-color: rgba($white, 1) !default;
688 | // $navbar-dark-active-color: $white !default;
689 | // $navbar-dark-disabled-color: rgba($white, .25) !default;
690 | // $navbar-dark-toggler-icon-bg: str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='#{$navbar-dark-color}' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E"), "#", "%23") !default;
691 | // $navbar-dark-toggler-border-color: rgba($white, .1) !default;
692 | //
693 | // $navbar-light-color: rgba($black, .5) !default;
694 | // $navbar-light-hover-color: rgba($black, .7) !default;
695 | // $navbar-light-active-color: rgba($black, .9) !default;
696 | // $navbar-light-disabled-color: rgba($black, .3) !default;
697 | // $navbar-light-toggler-icon-bg: str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='#{$navbar-light-color}' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E"), "#", "%23") !default;
698 | // $navbar-light-toggler-border-color: rgba($black, .1) !default;
699 | //
700 | // // Pagination
701 | //
702 | $pagination-padding-y: .75rem !default;
703 | $pagination-padding-x: 1rem !default;
704 | // $pagination-padding-y-sm: .25rem !default;
705 | // $pagination-padding-x-sm: .5rem !default;
706 | // $pagination-padding-y-lg: .75rem !default;
707 | // $pagination-padding-x-lg: 1.5rem !default;
708 | // $pagination-line-height: 1.25 !default;
709 | //
710 | // $pagination-color: $link-color !default;
711 | // $pagination-bg: $white !default;
712 | // $pagination-border-width: $border-width !default;
713 | // $pagination-border-color: $gray-300 !default;
714 | //
715 | // $pagination-focus-box-shadow: $input-btn-focus-box-shadow !default;
716 | // $pagination-focus-outline: 0 !default;
717 | //
718 | // $pagination-hover-color: $link-hover-color !default;
719 | // $pagination-hover-bg: $gray-200 !default;
720 | // $pagination-hover-border-color: $gray-300 !default;
721 | //
722 | // $pagination-active-color: $component-active-color !default;
723 | // $pagination-active-bg: $component-active-bg !default;
724 | // $pagination-active-border-color: $pagination-active-bg !default;
725 | //
726 | // $pagination-disabled-color: $gray-600 !default;
727 | // $pagination-disabled-bg: $white !default;
728 | // $pagination-disabled-border-color: $gray-300 !default;
729 | //
730 | //
731 | // // Jumbotron
732 | //
733 | // $jumbotron-padding: 2rem !default;
734 | // $jumbotron-bg: $gray-200 !default;
735 | //
736 | //
737 | // // Cards
738 | //
739 | // $card-spacer-y: .75rem !default;
740 | // $card-spacer-x: 1.25rem !default;
741 | // $card-border-width: $border-width !default;
742 | // $card-border-radius: $border-radius !default;
743 | $card-border-color: $lightblue !default;
744 | // $card-inner-border-radius: calc(#{$card-border-radius} - #{$card-border-width}) !default;
745 | // $card-cap-bg: rgba($black, .03) !default;
746 | // $card-bg: $white !default;
747 | //
748 | // $card-img-overlay-padding: 1.25rem !default;
749 | //
750 | // $card-group-margin: ($grid-gutter-width / 2) !default;
751 | // $card-deck-margin: $card-group-margin !default;
752 | //
753 | // $card-columns-count: 3 !default;
754 | // $card-columns-gap: 1.25rem !default;
755 | // $card-columns-margin: $card-spacer-y !default;
756 | //
757 | //
758 | // // Tooltips
759 | //
760 | // $tooltip-font-size: $font-size-sm !default;
761 | // $tooltip-max-width: 200px !default;
762 | // $tooltip-color: $white !default;
763 | // $tooltip-bg: $black !default;
764 | // $tooltip-border-radius: $border-radius !default;
765 | // $tooltip-opacity: .9 !default;
766 | // $tooltip-padding-y: .25rem !default;
767 | // $tooltip-padding-x: .5rem !default;
768 | // $tooltip-margin: 0 !default;
769 | //
770 | // $tooltip-arrow-width: .8rem !default;
771 | // $tooltip-arrow-height: .4rem !default;
772 | // $tooltip-arrow-color: $tooltip-bg !default;
773 | //
774 | //
775 | // // Popovers
776 | //
777 | // $popover-font-size: $font-size-sm !default;
778 | // $popover-bg: $white !default;
779 | // $popover-max-width: 276px !default;
780 | // $popover-border-width: $border-width !default;
781 | // $popover-border-color: rgba($black, .2) !default;
782 | // $popover-border-radius: $border-radius-lg !default;
783 | // $popover-box-shadow: 0 .25rem .5rem rgba($black, .2) !default;
784 | //
785 | // $popover-header-bg: darken($popover-bg, 3%) !default;
786 | // $popover-header-color: $headings-color !default;
787 | // $popover-header-padding-y: .5rem !default;
788 | // $popover-header-padding-x: .75rem !default;
789 | //
790 | // $popover-body-color: $body-color !default;
791 | // $popover-body-padding-y: $popover-header-padding-y !default;
792 | // $popover-body-padding-x: $popover-header-padding-x !default;
793 | //
794 | // $popover-arrow-width: 1rem !default;
795 | // $popover-arrow-height: .5rem !default;
796 | // $popover-arrow-color: $popover-bg !default;
797 | //
798 | // $popover-arrow-outer-color: fade-in($popover-border-color, .05) !default;
799 | //
800 | //
801 | // // Badges
802 | //
803 | $badge-font-size: 84% !default;
804 | // $badge-font-weight: $font-weight-bold !default;
805 | // $badge-padding-y: .25em !default;
806 | // $badge-padding-x: .4em !default;
807 | // $badge-border-radius: $border-radius !default;
808 | //
809 | // $badge-pill-padding-x: .6em !default;
810 | // // Use a higher than normal value to ensure completely rounded edges when
811 | // // customizing padding or font-size on labels.
812 | // $badge-pill-border-radius: 10rem !default;
813 | //
814 | //
815 | // // Modals
816 | //
817 | // // Padding applied to the modal body
818 | // $modal-inner-padding: 1rem !default;
819 | //
820 | // $modal-dialog-margin: .5rem !default;
821 | // $modal-dialog-margin-y-sm-up: 1.75rem !default;
822 | //
823 | // $modal-title-line-height: $line-height-base !default;
824 | //
825 | // $modal-content-bg: $white !default;
826 | // $modal-content-border-color: rgba($black, .2) !default;
827 | // $modal-content-border-width: $border-width !default;
828 | // $modal-content-border-radius: $border-radius-lg !default;
829 | // $modal-content-box-shadow-xs: 0 .25rem .5rem rgba($black, .5) !default;
830 | // $modal-content-box-shadow-sm-up: 0 .5rem 1rem rgba($black, .5) !default;
831 | //
832 | // $modal-backdrop-bg: $black !default;
833 | // $modal-backdrop-opacity: .5 !default;
834 | // $modal-header-border-color: $gray-200 !default;
835 | // $modal-footer-border-color: $modal-header-border-color !default;
836 | // $modal-header-border-width: $modal-content-border-width !default;
837 | // $modal-footer-border-width: $modal-header-border-width !default;
838 | // $modal-header-padding: 1rem !default;
839 | //
840 | // $modal-lg: 800px !default;
841 | // $modal-md: 500px !default;
842 | // $modal-sm: 300px !default;
843 | //
844 | // $modal-transition: transform .3s ease-out !default;
845 | //
846 | //
847 | // // Alerts
848 | // //
849 | // // Define alert colors, border radius, and padding.
850 | //
851 | // $alert-padding-y: .75rem !default;
852 | // $alert-padding-x: 1.25rem !default;
853 | // $alert-margin-bottom: 1rem !default;
854 | // $alert-border-radius: $border-radius !default;
855 | // $alert-link-font-weight: $font-weight-bold !default;
856 | // $alert-border-width: $border-width !default;
857 | //
858 | $alert-bg-level: 0 !default;
859 | $alert-border-level: 0 !default;
860 | $alert-color-level: 0 !default;
861 | //
862 | //
863 | // // Progress bars
864 | //
865 | // $progress-height: 1rem !default;
866 | // $progress-font-size: ($font-size-base * .75) !default;
867 | // $progress-bg: $gray-200 !default;
868 | // $progress-border-radius: $border-radius !default;
869 | // $progress-box-shadow: inset 0 .1rem .1rem rgba($black, .1) !default;
870 | // $progress-bar-color: $white !default;
871 | // $progress-bar-bg: theme-color("primary") !default;
872 | // $progress-bar-animation-timing: 1s linear infinite !default;
873 | // $progress-bar-transition: width .6s ease !default;
874 | //
875 | // // List group
876 | //
877 | // $list-group-bg: $white !default;
878 | // $list-group-border-color: rgba($black, .125) !default;
879 | // $list-group-border-width: $border-width !default;
880 | // $list-group-border-radius: $border-radius !default;
881 | //
882 | // $list-group-item-padding-y: .75rem !default;
883 | // $list-group-item-padding-x: 1.25rem !default;
884 | //
885 | // $list-group-hover-bg: $gray-100 !default;
886 | // $list-group-active-color: $component-active-color !default;
887 | // $list-group-active-bg: $component-active-bg !default;
888 | // $list-group-active-border-color: $list-group-active-bg !default;
889 | //
890 | // $list-group-disabled-color: $gray-600 !default;
891 | // $list-group-disabled-bg: $list-group-bg !default;
892 | //
893 | // $list-group-action-color: $gray-700 !default;
894 | // $list-group-action-hover-color: $list-group-action-color !default;
895 | //
896 | // $list-group-action-active-color: $body-color !default;
897 | // $list-group-action-active-bg: $gray-200 !default;
898 | //
899 | //
900 | // // Image thumbnails
901 | //
902 | // $thumbnail-padding: .25rem !default;
903 | // $thumbnail-bg: $body-bg !default;
904 | // $thumbnail-border-width: $border-width !default;
905 | // $thumbnail-border-color: $gray-300 !default;
906 | // $thumbnail-border-radius: $border-radius !default;
907 | // $thumbnail-box-shadow: 0 1px 2px rgba($black, .075) !default;
908 | //
909 | //
910 | // // Figures
911 | //
912 | // $figure-caption-font-size: 90% !default;
913 | // $figure-caption-color: $gray-600 !default;
914 | //
915 | //
916 | // // Breadcrumbs
917 | //
918 | // $breadcrumb-padding-y: .75rem !default;
919 | // $breadcrumb-padding-x: 1rem !default;
920 | // $breadcrumb-item-padding: .5rem !default;
921 | //
922 | // $breadcrumb-margin-bottom: 1rem !default;
923 | //
924 | // $breadcrumb-bg: $gray-200 !default;
925 | // $breadcrumb-divider-color: $gray-600 !default;
926 | // $breadcrumb-active-color: $gray-600 !default;
927 | // $breadcrumb-divider: quote("/") !default;
928 | //
929 | // $breadcrumb-border-radius: $border-radius !default;
930 | //
931 | //
932 | // // Carousel
933 | //
934 | // $carousel-control-color: $white !default;
935 | // $carousel-control-width: 15% !default;
936 | // $carousel-control-opacity: .5 !default;
937 | //
938 | // $carousel-indicator-width: 30px !default;
939 | // $carousel-indicator-height: 3px !default;
940 | // $carousel-indicator-spacer: 3px !default;
941 | // $carousel-indicator-active-bg: $white !default;
942 | //
943 | // $carousel-caption-width: 70% !default;
944 | // $carousel-caption-color: $white !default;
945 | //
946 | // $carousel-control-icon-width: 20px !default;
947 | //
948 | // $carousel-control-prev-icon-bg: str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='#{$carousel-control-color}' viewBox='0 0 8 8'%3E%3Cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3E%3C/svg%3E"), "#", "%23") !default;
949 | // $carousel-control-next-icon-bg: str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='#{$carousel-control-color}' viewBox='0 0 8 8'%3E%3Cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3E%3C/svg%3E"), "#", "%23") !default;
950 | //
951 | // $carousel-transition: transform .6s ease !default; // Define transform transition first if using multiple transitions (e.g., `transform 2s ease, opacity .5s ease-out`)
952 | //
953 | //
954 | // // Close
955 | //
956 | // $close-font-size: $font-size-base * 1.5 !default;
957 | // $close-font-weight: $font-weight-bold !default;
958 | // $close-color: $black !default;
959 | // $close-text-shadow: 0 1px 0 $white !default;
960 | //
961 | // // Code
962 | //
963 | // $code-font-size: 87.5% !default;
964 | // $code-color: $pink !default;
965 | //
966 | // $kbd-padding-y: .2rem !default;
967 | // $kbd-padding-x: .4rem !default;
968 | // $kbd-font-size: $code-font-size !default;
969 | // $kbd-color: $white !default;
970 | // $kbd-bg: $gray-900 !default;
971 | //
972 | // $pre-color: $gray-900 !default;
973 | // $pre-scrollable-max-height: 340px !default;
974 | //
975 | //
976 | // // Printing
977 | // $print-page-size: a3 !default;
978 | // $print-body-min-width: map-get($grid-breakpoints, "lg") !default;
979 |
980 |
981 | // --------------------------------------- !!
982 | // Additional theme variables
983 | // --------------------------------------- !!
984 |
985 | // Buttons
986 |
987 | $round-radius: 30px !default;
--------------------------------------------------------------------------------
/styles/main.scss:
--------------------------------------------------------------------------------
1 | //====================================
2 | // Global Imports
3 | //====================================
4 |
5 |
6 | // 1- Import bootstrap framework
7 |
8 | @import 'bootstrap-user-variables';
9 | @import '~bootstrap/scss/bootstrap';
10 |
11 | //====================================
12 | // Additional theme classes
13 | //====================================
14 |
15 | // General
16 | body {
17 | overflow-x: hidden;
18 | }
19 | .first-container {
20 | padding-top: 69.75px;
21 | }
22 | .sticky-top {
23 | top: 69.75px;
24 | }
25 | .secondfont, .navbar-brand {
26 | font-family: $font-secondary;
27 | }
28 | img {
29 | max-width:100%;
30 | }
31 | .tofront {
32 | position:relative;
33 | z-index:1;
34 | }
35 | .full-width {
36 | width: 100vw;
37 | position: relative;
38 | margin-left: -50vw;
39 | left: 50%;
40 | }
41 | a, a:hover {transition: all .2s;}
42 | a {
43 | color: $success;
44 | }
45 | a.text-dark:hover {
46 | color: $success !important;
47 | }
48 | .c-pointer:hover {
49 | cursor:pointer;
50 | }
51 | .z-index-1 {
52 | z-index:1;
53 | }
54 | // Typography
55 |
56 | .display-3 {
57 | @include media-breakpoint-down(md) {
58 | font-size: 3.5rem;
59 | }
60 | @include media-breakpoint-down(sm) {
61 | font-size: 2rem;
62 | }
63 | }
64 |
65 |
66 | // Margins
67 | .row.gap-y>.col, .row.gap-y>[class*="col-"] {
68 | padding-top: 15px;
69 | padding-bottom: 15px;
70 | }
71 | .mt-neg5 {
72 | margin-top: -5rem;
73 | }
74 | .ml-neg5 {
75 | margin-left: -5rem;
76 | }
77 |
78 | // Heights
79 | @include media-breakpoint-up(md) {
80 | .h-md-100-v {
81 | height: 100vh;
82 | }
83 | .h-md-100 {
84 | height: 100vh;
85 | }
86 | }
87 |
88 | @include media-breakpoint-only(xl) {
89 | .h-xl-300 {
90 | height: 300px;
91 | }
92 | .h-max-380 {
93 | max-height:380px;
94 | }
95 | }
96 |
97 | // Mixins
98 | @mixin button-variant-link($color){
99 | color: $color;
100 | }
101 |
102 | // Buttons
103 | .btn-round {
104 | border-radius: $round-radius !important;
105 | }
106 | .btn {
107 | font-family: Source Sans Pro;
108 | @include button-size($btn-padding-y, $btn-padding-x, $font-size-base, $btn-line-height, $btn-border-radius);
109 | &:hover,
110 | &:focus {
111 | box-shadow: $box-shadow;
112 | outline: 0 !important;
113 | }
114 | .badge {
115 | position: absolute;
116 | top: -.625rem;
117 | right: -.3125rem;
118 | }
119 | position:relative;
120 | }
121 |
122 | .btn-lg {
123 | @include button-size($btn-padding-y-lg, $btn-padding-x-lg, $font-size-lg, $btn-line-height-lg, $btn-border-radius-lg);
124 | }
125 |
126 | .btn-sm {
127 | @include button-size($btn-padding-y-sm, $btn-padding-x-sm, $font-size-sm, $btn-line-height-sm, $btn-border-radius-sm);
128 | }
129 |
130 | .btn-link {
131 | &:hover,
132 | &:focus {
133 | box-shadow:none;
134 | }
135 | }
136 |
137 | @each $color, $value in $theme-colors {
138 | .btn-link.btn-#{$color} {
139 | @include button-variant-link($value);
140 | border-color: transparent;
141 | }
142 | }
143 | .btn-white {background-color:#fff;}
144 | // Inputs
145 | .input-round {
146 | border-radius: $round-radius !important;
147 | }
148 | .input-group.input-round {
149 | input:first-child {border-radius:30px 0 0 30px}
150 | input:last-child {border-radius:0px 30px 30px 0px}
151 | }
152 |
153 |
154 | // Nav
155 | .navbar {
156 | box-shadow:0 0px 1px 0 rgba(0, 0, 0, 0.05);
157 | transition: all .08s;
158 | font-weight: $font-weight-normal;
159 | .highlight .nav-link {color: $primary !important; border:1px solid $success; padding: 0.3rem 1rem; border-radius: 3px;font-size:.93rem;}
160 | .highlight .nav-link:hover {background: $success; color:#fff !important;}
161 | }
162 | .navbar-brand {
163 | margin-right:2rem;
164 | font-size:1.25rem;
165 | }
166 | .scrollednav {
167 |
168 | }
169 | .dropdown-item {
170 | font-weight: $font-weight-base;
171 | }
172 | .dropdown-menu {
173 | border:0;
174 | text-transform:none;
175 | box-shadow: $box-shadow-lg;
176 | &:before {
177 | @include media-breakpoint-up(md) {
178 | content: '';
179 | top: -8px;
180 | position: absolute;
181 | left: 50px;
182 | border-top: 16px solid #fff;
183 | border-left: 16px solid #fff;
184 | transform: rotate(45deg);
185 | }
186 | }
187 | }
188 |
189 | // Dark links against a light background
190 | .navbar-light {
191 | .navbar-brand {
192 | color: $navbar-light-active-color;
193 |
194 | @include hover-focus {
195 | color: $navbar-light-active-color;
196 | }
197 | }
198 |
199 | .navbar-nav {
200 | .nav-link {
201 | color: $navbar-light-color;
202 | @include hover-focus {
203 | color: $navbar-light-hover-color;
204 | }
205 |
206 | &.disabled {
207 | color: $navbar-light-disabled-color;
208 | }
209 | }
210 |
211 | .show > .nav-link,
212 | .active > .nav-link,
213 | .nav-link.show,
214 | .nav-link.active {
215 | color: $navbar-light-active-color;
216 | }
217 | }
218 |
219 | .navbar-toggler {
220 | color: $navbar-light-color;
221 | border-color: $navbar-light-toggler-border-color;
222 | }
223 |
224 | .navbar-toggler-icon {
225 | background-image: $navbar-light-toggler-icon-bg;
226 | }
227 |
228 | .navbar-text {
229 | color: $navbar-light-color;
230 | a {
231 | color: $navbar-light-active-color;
232 |
233 | @include hover-focus {
234 | color: $navbar-light-active-color;
235 | }
236 | }
237 | }
238 | }
239 |
240 |
241 | // Jumbotron
242 | .jumbotron {
243 | background-size:cover;
244 | padding: 7rem 1rem;
245 | }
246 | @include media-breakpoint-up(lg) {
247 | .jumbotron-lg-withnav {padding-bottom: calc(10rem - 69.75px);}
248 | .jumbotron-lg { padding:10rem 0;}
249 | .jumbotron-xl { padding:15rem 0;}
250 | .jumbotron-xl {min-height:100vh;}
251 | .bottom-align-text-absolute {
252 | position: absolute;
253 | bottom: 30px;
254 | margin:auto;
255 | left: 0;
256 | right:0;
257 | }
258 | }
259 |
260 | // Backgrounds
261 | .bg-black { background-color: $black; }
262 | .overlay {
263 | position: relative;
264 | .container {
265 | position: relative;
266 | }
267 | &:before {
268 | content: "";
269 | display: block;
270 | height: 100%;
271 | left: 0;
272 | top: 0;
273 | position: absolute;
274 | width: 100%;
275 | }
276 | }
277 | .overlay-black {
278 | &:before {
279 | background-color: rgba(0, 0, 0, 0.5);
280 | }
281 | }
282 | .overlay-blue {
283 | &:before {
284 | background-color: rgba(23, 29, 90, 0.5);
285 | }
286 | }
287 | .overlay-red {
288 | &:before {
289 | background:linear-gradient(0deg,rgba(44,44,44,.2),rgba(224,23,3,.6));
290 | }
291 | }
292 | .overlay-blue {
293 | &:before {
294 | background-color: rgba(23, 29, 90, 0.5);
295 | }
296 | }
297 |
298 | // SVG fills
299 |
300 | @mixin fill-variant($parent, $color) {
301 | #{$parent} {
302 | fill: $color !important;
303 | }
304 | }
305 |
306 | @each $color, $value in $theme-colors {
307 | @include fill-variant(".bg-#{$color}", $value);
308 | }
309 |
310 | // Cards
311 | .card {
312 | .date {
313 | position: absolute;
314 | top: 20px;
315 | right: 20px;
316 | z-index: 1;
317 | background: $danger;
318 | width: 55px;
319 | height: 55px;
320 | padding: 12.5px 0;
321 | -webkit-border-radius: 100%;
322 | -moz-border-radius: 100%;
323 | border-radius: 100%;
324 | color: #FFFFFF;
325 | font-weight: 700;
326 | text-align: center;
327 | -webkti-box-sizing: border-box;
328 | -moz-box-sizing: border-box;
329 | box-sizing: border-box;
330 | .day {
331 | font-size: 16px;
332 | line-height:1;
333 | }
334 | .month {
335 | font-size: 11px;
336 | text-transform: uppercase;
337 | }
338 | }
339 | a:hover {text-decoration:none; color: $primary;}
340 | }
341 |
342 | .card-pricing .card ul li {
343 | margin-bottom:1.5rem;
344 | }
345 |
346 | // Icons
347 | .iconbox {
348 | border:1px solid;
349 | text-align:center;
350 | display:inline-block;
351 | }
352 | .iconbox.iconsmall {
353 | width:40px;
354 | height:40px;
355 | line-height:40px;
356 | font-size:1rem;
357 | }
358 | .iconbox.iconmedium {
359 | width:60px;
360 | height:60px;
361 | line-height:60px;
362 | font-size:1.8rem;
363 | }
364 | .iconbox.iconlarge {
365 | width:80px;
366 | height:80px;
367 | line-height:80px;
368 | font-size:2.2rem;
369 | }
370 |
371 | // Alerts
372 | @each $color, $value in $theme-colors {
373 | .alert-#{$color} {
374 | @include alert-variant(theme-color-level($color, $alert-bg-level), theme-color-level($color, $alert-border-level), #fff);
375 | }
376 | }
377 |
378 | // Lists
379 |
380 | ul.list-unstyled li {margin-bottom:.3rem;}
381 | ol.list-featured {
382 | counter-reset: my-awesome-counter;
383 | list-style: none;
384 | padding-left:0;
385 | }
386 | ol.list-featured li {
387 | counter-increment: my-awesome-counter;
388 | display: flex;
389 | font-size: 0.8rem;
390 | }
391 | ol.list-featured li:before {
392 | content: "0" counter(my-awesome-counter);
393 | font-weight: bold;
394 | font-size: 3rem;
395 | margin-right: 0.5rem;
396 | font-family: 'Abril Fatface', serif;
397 | line-height: 1;
398 | }
399 |
400 | // Login
401 |
402 | @include media-breakpoint-up(md) {
403 | .loginarea {
404 | z-index: 1111;
405 | }
406 | }
407 |
408 | // Article
409 | article {
410 | font-family: "sans-serif", "Georgia";
411 | font-size:20px;
412 | line-height:1.7;
413 | p, pre, figure, img, blockquote, iframed, embed {
414 | margin-bottom:2rem;
415 | }
416 | blockquote {
417 | padding-left:40px;
418 | margin-left:0px;
419 | font-style:italic;
420 | position:relative;
421 | }
422 | blockquote:before {
423 | content: "“";
424 | font-family: Georgia;
425 | font-size: 8rem;
426 | margin: -1rem 2rem 0 -3.9rem;
427 | position: absolute;
428 | opacity: 1;
429 | float: left;
430 | line-height: 1;
431 | }
432 | &:first-letter {float: left;
433 | font-size: 5em;line-height:1;
434 | margin: 0 .2em 0 0;vertical-align: top;}
435 | img {
436 | max-width: 100%;
437 | }
438 | }
439 |
440 | .spanborder {
441 | border-bottom: 1px solid $lightblue;
442 | margin-bottom:2rem;
443 | }
444 | .spanborder span {
445 | border-bottom: 1px solid rgba(0,0,0,.44);
446 | display: inline-block;
447 | padding-bottom: 20px;
448 | margin-bottom: -1px;
449 | }
450 |
451 | .a2a_default_style .a2a_svg {
452 | border-radius:50% !important;
453 | margin-top:.5rem;
454 | margin-left:auto;
455 | margin-right:auto;
456 | }
457 | @include media-breakpoint-down(lg) {
458 | .display-4 {font-size:35px;}
459 | }
460 | @include media-breakpoint-down(md) {
461 | .display-4 {font-size:25px;}
462 | }
463 | @include media-breakpoint-up(lg) {
464 | .a2a_default_style a {
465 | display:block !important;
466 | float:none !important;
467 | }
468 | .a2a_default_style .a2a_svg {
469 | border-radius:50% !important;
470 | display:block !important;
471 | float:none !important;
472 | margin-top:.5rem;
473 | margin-left:auto;
474 | margin-right:auto;
475 | }
476 | .a2a_svg {
477 | height: 40px !important;
478 | line-height: 40px !important;
479 | width: 40px !important;
480 | }
481 | }
482 | // Footer
483 | footer.bg-black {
484 | color: $gray-600;
485 | a {
486 | color: $gray-600;
487 | &:hover {
488 | text-decoration:none;
489 | color: $white;
490 | }
491 | }
492 | }
493 |
494 | .article-post {
495 | img {
496 | max-width: 100%;
497 | }
498 | blockquote {
499 | white-space: break-spaces;
500 | }
501 | }
502 |
--------------------------------------------------------------------------------
/tests/setupTests.ts:
--------------------------------------------------------------------------------
1 | import Enzyme from 'enzyme';
2 | import Adapter from 'enzyme-adapter-react-16';
3 |
4 | // Configure Enzyme with React 16 adapter
5 | Enzyme.configure({ adapter: new Adapter() });
6 |
--------------------------------------------------------------------------------
/tsconfig.jest.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "jsx": "react"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "strict": false,
12 | "forceConsistentCasingInFileNames": true,
13 | "noEmit": true,
14 | "esModuleInterop": true,
15 | "module": "esnext",
16 | "moduleResolution": "node",
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "jsx": "preserve",
20 | "baseUrl": ".",
21 | "paths": {
22 | "@/components": [
23 | "./components/index.ts"
24 | ],
25 | "@/core/*": [
26 | "./core/*"
27 | ],
28 | "@/config": [
29 | "./config/index.ts"
30 | ]
31 | }
32 | },
33 | "exclude": [
34 | "node_modules"
35 | ],
36 | "include": [
37 | "next-env.d.ts",
38 | "**/*.ts",
39 | "**/*.tsx"
40 | ]
41 | }
42 |
--------------------------------------------------------------------------------