├── .editorconfig ├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── docs └── assets │ └── freebies-hunt-logo.svg ├── gatsby-browser.js ├── gatsby-config.js ├── gatsby-node.js ├── gatsby-ssr.js ├── package-lock.json ├── package.json ├── src ├── components │ ├── categoryCard.js │ ├── categoryCard.scss │ ├── categoryGrid.js │ ├── categoryGrid.scss │ ├── categoryGridSearchEngine.js │ ├── categoryGridSearchEngine.scss │ ├── externalLink.js │ ├── footer.js │ ├── footer.scss │ ├── freebieList.js │ ├── freebieList.scss │ ├── freebieSearchInterface.js │ ├── header.js │ ├── header.scss │ ├── icon.js │ ├── layout.js │ ├── layout.scss │ ├── seo.js │ ├── textInput.js │ ├── userLinks.js │ └── userLinks.scss ├── config.js ├── iconExtract.js ├── images │ ├── 404.png │ ├── arrow-up.svg │ ├── category-icons │ │ ├── algorithm.svg │ │ ├── articles.svg │ │ ├── audio.svg │ │ ├── blog.svg │ │ ├── cad.svg │ │ ├── cdn.svg │ │ ├── clock.svg │ │ ├── communication.svg │ │ ├── community.svg │ │ ├── courses.svg │ │ ├── documentation.svg │ │ ├── game-controller.svg │ │ ├── games.svg │ │ ├── hardware.svg │ │ ├── ide.svg │ │ ├── inspiration.svg │ │ ├── list.svg │ │ ├── math.svg │ │ ├── music-note.svg │ │ ├── newsletter.svg │ │ ├── oer.svg │ │ ├── online-learning.svg │ │ ├── open-content.svg │ │ ├── operating-system.svg │ │ ├── paintbrush.svg │ │ ├── platform.svg │ │ ├── standards.svg │ │ ├── stocks.svg │ │ ├── student-packs.svg │ │ ├── study.svg │ │ ├── textbook.svg │ │ ├── uncategorized.svg │ │ ├── web-browsers.svg │ │ └── youtube.svg │ ├── freebies-hunt-logo.svg │ └── social-icons │ │ ├── email.svg │ │ ├── github.svg │ │ └── twitter.svg ├── scripts.js └── templates │ ├── categories.js │ ├── category.js │ └── category.scss └── static ├── category-icons.svg ├── freebies-hunt-logo.svg └── social-icons.svg /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.{js,py}] 4 | charset = utf-8 5 | 6 | [*.{js,jsx,ts,tsx,vue}] 7 | indent_style = space 8 | indent_size = 2 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # dotenv environment variables file 55 | .env 56 | 57 | # gatsby files 58 | .cache/ 59 | public 60 | 61 | # Mac files 62 | .DS_Store 63 | 64 | # Yarn 65 | yarn-error.log 66 | .pnp/ 67 | .pnp.js 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "endOfLine": "lf", 3 | "semi": false, 4 | "singleQuote": false, 5 | "tabWidth": 2, 6 | "trailingComma": "es5" 7 | } 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # freebies-hunt 2 | 3 | ![Freebies Hunt logo](./docs/assets/freebies-hunt-logo.svg) 4 | 5 | Want to find some free stuff? I've compiled a list of freebies that accumulated for almost a year now and there's no better way of sharing it than making it as a fully fledged website, of course. 6 | 7 | Recently, I'm really getting enthustiastic about free and open software (or FOSS) and open content, in general. With some of the products are going competitive with the premium products such as [MuseScore](https://musescore.org/), [Visual Studio Code](https://code.visualstudio.com/) and [OpenStax](https://openstax.org/), I'm really thrilled looking into the future with these open content floating around. So why not bundle them up and compile them in a list. 8 | 9 | If you want to explore into the world of open content, here's a bit of a gateway for you. It's not complete compared to [other resource lists](https://github.com/sindresorhus/awesome) but I tried to keep it small (saying while sitting at a count of 250 resources, lol). 10 | 11 | ## Contributing 12 | Want to contribute? Feel free to do so. Not only you get to at least experience contributing to an open source project but it improves this project as well. 13 | 14 | If you want to suggest some additional resources, you can file an issue here but I recommend it to file it [in the API repo of the site instead](https://github.com/foo-dogsquared/freebies-hunt-api) so that I can easily see it there. 15 | 16 | If you want to contribute through code, just do the following: 17 | 18 | - fork the repo 19 | - clone the forked repo (`git clone `) 20 | - go to the project directory 21 | - install the dependencies (`npm i`) 22 | - create and checkout in the new branch (`git checkout -b `) 23 | - open it in your local machine (`npm run develop`) 24 | 25 | This is built with [Gatsby](http://gatsbyjs.org/) (and by extension, [React](http://reactjs.org/)) so at least familiarize yourself a bit with those things. 26 | 27 | Also, this serves as a production use case for my [JSON database management system](https://github.com/foo-dogsquared/jayson-db) and it uses the [Freebies Hunt API](https://github.com/foo-dogsquared/freebies-hunt-api) which it's available as an [npm package](https://www.npmjs.com/package/freebies-hunt-api). So at least take a closer look at those to get a bigger picture on how the site is built. I'll put up a semi-detailed documentation on it soon but for now, [take a look on how the API was built](https://github.com/foo-dogsquared/freebies-hunt-api/blob/master/docs/getting-started.md). 28 | 29 | ## Additional notes 30 | The logo is made by me with [Figma](http://figma.com/) (hey, one of my recommendations in the list 😉). ~~Also, gRaPhIcS dEsIgN iS mY PaSsiOn nOw~~ 31 | 32 | Additional icons used in the site belongs to [Freepik](https://www.freepik.com/). 33 | 34 | This project is under [GNU General Public License v3.0](https://choosealicense.com/licenses/gpl-3.0/). 35 | -------------------------------------------------------------------------------- /docs/assets/freebies-hunt-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /gatsby-browser.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Implement Gatsby's Browser APIs in this file. 3 | * 4 | * See: https://www.gatsbyjs.org/docs/browser-apis/ 5 | */ 6 | 7 | // You can delete this file if you're not using it 8 | -------------------------------------------------------------------------------- /gatsby-config.js: -------------------------------------------------------------------------------- 1 | const config = require("./src/config"); 2 | 3 | module.exports = { 4 | plugins: [ 5 | `gatsby-plugin-sass`, 6 | `gatsby-plugin-react-helmet`, 7 | { 8 | resolve: `gatsby-plugin-react-svg`, 9 | options: { 10 | rule: { 11 | include: /images/ 12 | } 13 | } 14 | }, 15 | { 16 | resolve: `gatsby-plugin-manifest`, 17 | options: { 18 | name: config.title, 19 | short_name: config.title, 20 | start_url: `/`, 21 | background_color: config.mainColor, 22 | theme_color: config.mainColor, 23 | display: `standalone`, 24 | icon: `src/images/freebies-hunt-logo.svg`, // This path is relative to the root of the site. 25 | }, 26 | }, 27 | { 28 | resolve: `gatsby-plugin-google-analytics`, 29 | options: { 30 | trackingId: ` UA-126718538-4`, 31 | head: true, 32 | anonymize: true, 33 | } 34 | }, 35 | 'gatsby-plugin-offline', 36 | ], 37 | } 38 | -------------------------------------------------------------------------------- /gatsby-node.js: -------------------------------------------------------------------------------- 1 | const freebiesHuntApi = require("freebies-hunt-api"); 2 | const { kebabCase } = require("./src/scripts") 3 | 4 | exports.createPages = async ({ actions: { createPage } }) => { 5 | const { categorizedData } = freebiesHuntApi; 6 | 7 | createPage({ 8 | path: '/', 9 | component: require.resolve("./src/templates/categories.js"), 10 | context: { 11 | categories: categorizedData 12 | } 13 | }) 14 | 15 | for (const category in categorizedData) { 16 | const categoryObject = categorizedData[category]; 17 | 18 | createPage({ 19 | path: `/${kebabCase(category)}`, 20 | component: require.resolve("./src/templates/category.js"), 21 | context: { 22 | category: categoryObject, 23 | name: category, 24 | categories: categorizedData, 25 | } 26 | }) 27 | } 28 | }; 29 | 30 | // adding some webpack configuration 31 | exports.onCreateWebpackConfig = ({ actions }) => { 32 | actions.setWebpackConfig({ 33 | node: { 34 | fs: 'empty', 35 | path: true, 36 | __dirname: true 37 | } 38 | }) 39 | } 40 | -------------------------------------------------------------------------------- /gatsby-ssr.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Implement Gatsby's SSR (Server Side Rendering) APIs in this file. 3 | * 4 | * See: https://www.gatsbyjs.org/docs/ssr-apis/ 5 | */ 6 | 7 | // You can delete this file if you're not using it 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "freebies-hunt", 3 | "description": "A website of free and open source resources.", 4 | "license": "MIT", 5 | "scripts": { 6 | "svg:build": "node ./src/iconExtract.js", 7 | "develop": "npm run svg:build && gatsby develop", 8 | "build": "npm run svg:build && gatsby build --prefix-paths", 9 | "serve": "gatsby serve", 10 | "update:api": "npm i freebies-hunt-api@latest" 11 | }, 12 | "dependencies": { 13 | "color": "3.1.0", 14 | "freebies-hunt-api": "^1.5.2", 15 | "gatsby": "2.3.25", 16 | "gatsby-plugin-google-analytics": "2.0.18", 17 | "gatsby-plugin-manifest": "^2.0.26", 18 | "gatsby-plugin-offline": "2.0.25", 19 | "gatsby-plugin-react-helmet": "^3.0.11", 20 | "gatsby-plugin-react-svg": "^2.1.1", 21 | "gatsby-plugin-sass": "^2.0.11", 22 | "js-search": "1.4.2", 23 | "marked": "0.7.0", 24 | "node-sass": "^4.12.0", 25 | "react": "^16.5.1", 26 | "react-dom": "^16.5.1", 27 | "react-helmet": "^5.2.0", 28 | "svg-sprite": "^1.5.0", 29 | "tinycolor2": "^1.4.1" 30 | }, 31 | "version": "1.0.0", 32 | "main": "index.js", 33 | "author": "Gabriel Arazas " 34 | } 35 | -------------------------------------------------------------------------------- /src/components/categoryCard.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { Link } from "gatsby" 3 | 4 | import Icon from "./icon" 5 | 6 | import { kebabCase, generateStyleObject } from "../scripts" 7 | 8 | import "./categoryCard.scss" 9 | 10 | const categoryCard = ({ name, mainColor, iconName }) => ( 11 |
12 | 13 |
14 | 15 |
16 |
{name}
17 | 18 |
19 | ) 20 | 21 | export default categoryCard 22 | -------------------------------------------------------------------------------- /src/components/categoryCard.scss: -------------------------------------------------------------------------------- 1 | $border-style: currentColor 2px solid; 2 | 3 | .category-link { 4 | display: flex; 5 | flex-flow: column wrap; 6 | align-items: stretch; 7 | height: 100%; 8 | } 9 | 10 | .category-image { 11 | padding: 1em; 12 | } 13 | 14 | .category-card { 15 | min-height: 2em; 16 | width: 150px; 17 | margin: 1.5em .5em; 18 | text-align: center; 19 | border: $border-style; 20 | 21 | @media screen and (min-width: 400px) {width: 200px;} 22 | @media screen and (min-width: 600px) {width: 250px;} 23 | 24 | .category-icon { 25 | $size: 7em; 26 | height: $size; 27 | } 28 | 29 | .description { 30 | color: black; 31 | min-height: 2em; 32 | height: auto; 33 | vertical-align: middle; 34 | padding: 1em; 35 | background: whitesmoke; 36 | flex: 1 0 auto; 37 | border-top: $border-style; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/components/categoryGrid.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import CategoryCard from "./categoryCard" 3 | import color from "color" 4 | 5 | import "./categoryGrid.scss" 6 | 7 | const categoryGrid = ({ categories }) => { 8 | // TODO: 9 | // Make the categories to be an array 10 | // Sort them by their `mainColor` 11 | // Render the cards with the sorted by color list 12 | // PROFIT! 13 | 14 | categories.sort((current, next) => { 15 | // color instances 16 | const currentName = current.name.toLowerCase() 17 | const nextName = next.name.toLowerCase() 18 | if (currentName > nextName) { 19 | return 1; 20 | } else if (currentName < nextName) { 21 | return -1; 22 | } else { 23 | return 0; 24 | } 25 | }); 26 | 27 | return ( 28 |
29 | {categories.map((category) => { 30 | return 31 | })} 32 |
33 | ) 34 | } 35 | 36 | export default categoryGrid 37 | -------------------------------------------------------------------------------- /src/components/categoryGrid.scss: -------------------------------------------------------------------------------- 1 | .categories-grid { 2 | align-content: stretch; 3 | display: flex; 4 | flex-flow: row wrap; 5 | justify-content: space-evenly; 6 | } 7 | -------------------------------------------------------------------------------- /src/components/categoryGridSearchEngine.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react" 2 | import * as jsSearch from "js-search" 3 | 4 | import CategoryGrid from "./categoryGrid" 5 | 6 | import "./categoryGridSearchEngine.scss" 7 | 8 | class CategorySearch extends Component { 9 | constructor(props) { 10 | super(props); 11 | this.state = { 12 | search: [], 13 | searchQuery: ``, 14 | searchResult: [], 15 | } 16 | 17 | this.categories = {} 18 | } 19 | 20 | // it's an arrow function to bind it into the parent class 21 | searchData = (event) => { 22 | const { search } = this.state; 23 | const searchQuery = event.target.value; 24 | const searchQueryResult = search.search(searchQuery); 25 | this.setState({ searchQuery, searchResult: searchQueryResult }); 26 | } 27 | 28 | componentDidMount() { 29 | const indexes = [ 30 | 'name', 31 | 'mainColor', 32 | 'children', 33 | 'description', 34 | 'iconName', 35 | ] 36 | // setting up the search engine 37 | const jsSearchInstance = new jsSearch.Search("name"); 38 | jsSearchInstance.indexStrategy = new jsSearch.AllSubstringsIndexStrategy() 39 | 40 | indexes.forEach(index => jsSearchInstance.addIndex(index)); 41 | jsSearchInstance.addDocuments(Object.values(this.categories)); 42 | 43 | this.setState({ search: jsSearchInstance }); 44 | } 45 | 46 | render() { 47 | const { categories } = this.props; 48 | const { searchQuery, searchResult } = this.state; 49 | const searchResults = (searchQuery) ? searchResult : Object.values(this.categories); 50 | this.categories = categories; 51 | 52 | return ( 53 |
54 | Search for category 55 | 56 | 57 |
58 | ) 59 | } 60 | } 61 | 62 | export default CategorySearch; 63 | -------------------------------------------------------------------------------- /src/components/categoryGridSearchEngine.scss: -------------------------------------------------------------------------------- 1 | .category-search, .freebie-search { 2 | width: 100%; 3 | font-size: 1.25em; 4 | padding: .5em; 5 | border: 0; 6 | } 7 | -------------------------------------------------------------------------------- /src/components/externalLink.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | /** 4 | * Simply a wrapper component for external links. 5 | * @param {String} className - list of class names separated by whitespace 6 | * @param {Object} style - apply JSX stylings 7 | * @param {String} href 8 | * @param {*} children 9 | * @param {*} value - the text to be inserted inside the anchor node; take note that once there is a value for children, it'll be overriden 10 | */ 11 | const ExternalLink = ({ className, style, href, children, value, dangerouslySetInnerHTML }) => { 12 | const valueToBePut = children || value; 13 | 14 | return ( 15 | { valueToBePut } 16 | ) 17 | } 18 | 19 | export default ExternalLink; 20 | -------------------------------------------------------------------------------- /src/components/footer.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | import config from "../config" 4 | 5 | import "./footer.scss" 6 | 7 | const Footer = ({ className, style }) => { 8 | return ( 9 | 14 | ) 15 | } 16 | 17 | export default Footer; 18 | -------------------------------------------------------------------------------- /src/components/footer.scss: -------------------------------------------------------------------------------- 1 | footer { 2 | p {margin-bottom: 1em;} 3 | 4 | & > :last-child {margin-bottom: 0;} 5 | } -------------------------------------------------------------------------------- /src/components/freebieList.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | // npm scripts 4 | import marked from "marked" 5 | 6 | // components 7 | import ExternalLink from "./externalLink" 8 | 9 | // CSS stylings 10 | import "./freebieList.scss" 11 | 12 | const FreebieList = ({ freebies }) => { 13 | return ( 14 |
    15 | {freebies.map(item => { 16 | let description = null; 17 | let personalComment = null; 18 | 19 | if (item.description) { 20 | description =

    ; 21 | } 22 | 23 | if (item.personalComment) { 24 | personalComment = ( 25 |
    26 | Personal comment: 27 |

    28 |
    29 | ) 30 | } 31 | 32 | return ( 33 |
  • 34 |

    35 | {description} 36 | {personalComment} 37 |
  • 38 | ) 39 | })} 40 |
41 | ) 42 | } 43 | 44 | export default FreebieList; 45 | -------------------------------------------------------------------------------- /src/components/freebieList.scss: -------------------------------------------------------------------------------- 1 | .freebies { 2 | list-style: none; 3 | padding: 0; 4 | margin: 0 auto; 5 | } 6 | 7 | .freebie-item { 8 | background: white; 9 | border-radius: 5px; 10 | border: solid currentColor 3px; 11 | color: black; 12 | line-height: 2em; 13 | padding: .5em; 14 | margin: 1em auto; 15 | position: relative; 16 | text-align: center; 17 | 18 | @media only screen and (min-width: 600px) { 19 | padding: 2em; 20 | } 21 | } 22 | 23 | .personal-comment { 24 | $border: 1px black solid; 25 | border: $border; 26 | font-size: 15px; 27 | margin-top: 1.25rem; 28 | padding: .25rem; 29 | text-align: left; 30 | } -------------------------------------------------------------------------------- /src/components/freebieSearchInterface.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react" 2 | 3 | // npm scripts 4 | import * as jsSearch from "js-search" 5 | 6 | // components 7 | import FreebieList from "./freebieList" 8 | 9 | class FreebieSearchInterface extends Component { 10 | constructor(props) { 11 | super(props); 12 | this.state = { 13 | search: [], 14 | searchQuery: ``, 15 | searchResult: [], 16 | } 17 | 18 | this.freebies = []; 19 | } 20 | 21 | searchData = (event) => { 22 | const { search } = this.state; 23 | const searchQuery = event.target.value; 24 | const searchQueryResult = search.search(searchQuery); 25 | this.setState({ searchQuery, searchResult: searchQueryResult }); 26 | } 27 | 28 | componentDidMount() { 29 | const indexes = [ 30 | 'name', 31 | 'url', 32 | 'description', 33 | 'personalComment' 34 | ]; 35 | 36 | const jsSearchInstance = new jsSearch.Search("name"); 37 | jsSearchInstance.indexStrategy = new jsSearch.AllSubstringsIndexStrategy() 38 | 39 | indexes.forEach(index => jsSearchInstance.addIndex(index)); 40 | 41 | jsSearchInstance.addDocuments(this.freebies); 42 | 43 | this.setState({ search: jsSearchInstance }); 44 | } 45 | 46 | render() { 47 | const { freebies } = this.props; 48 | const { searchQuery, searchResult } = this.state 49 | this.freebies = freebies; 50 | 51 | const searchResults = (searchQuery) ? searchResult : freebies; 52 | 53 | return ( 54 |
55 | Quickly search for a freebie 56 | 57 | 58 |
59 | ) 60 | } 61 | } 62 | 63 | export default FreebieSearchInterface; 64 | -------------------------------------------------------------------------------- /src/components/header.js: -------------------------------------------------------------------------------- 1 | import { Link } from "gatsby" 2 | import PropTypes from "prop-types" 3 | import React from "react" 4 | 5 | import Logo from "../images/freebies-hunt-logo.svg" 6 | 7 | import "./header.scss" 8 | 9 | const Header = ({ siteTitle }) => { 10 | return (
11 | 12 | 13 |

{siteTitle}

14 | 15 |
) 16 | } 17 | 18 | Header.propTypes = { 19 | siteTitle: PropTypes.string, 20 | } 21 | 22 | Header.defaultProps = { 23 | siteTitle: ``, 24 | } 25 | 26 | export default Header 27 | -------------------------------------------------------------------------------- /src/components/header.scss: -------------------------------------------------------------------------------- 1 | header { 2 | color: black; 3 | margin: 1em auto; 4 | margin-bottom: 3em; 5 | 6 | svg { 7 | height: 8em; 8 | } 9 | } 10 | 11 | .header-link { 12 | align-items: center; 13 | display: flex; 14 | flex-flow: column wrap; 15 | justify-content: center; 16 | text-align: center; 17 | text-decoration: none; 18 | } -------------------------------------------------------------------------------- /src/components/icon.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | /** 4 | * Icon Component 5 | * @param {String} file - the name of the SVG (from `static` directory) 6 | * @param {String} name - the name of the to be used 7 | * @param {String} className - an array of classes to be styled with the component 8 | * @param {String} style - the inline CSS style 9 | * 10 | * @result {JSX Component} 11 | */ 12 | const icon = ({ file, name, className, style }) => { 13 | return ( 14 | 15 | 16 | 17 | ) 18 | } 19 | 20 | // export it 21 | export default icon; -------------------------------------------------------------------------------- /src/components/layout.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Layout component that queries for data 3 | * with Gatsby's StaticQuery component 4 | * 5 | * See: https://www.gatsbyjs.org/docs/static-query/ 6 | */ 7 | 8 | import React from "react" 9 | import PropTypes from "prop-types" 10 | import { generateColorPallete } from "../scripts" 11 | 12 | // importing of the usual configuration 13 | import config from "../config" 14 | 15 | // import of components 16 | import Header from "./header" 17 | import Footer from "./footer" 18 | import UserLinks from "./userLinks" 19 | import "./layout.scss" 20 | 21 | // import of image 22 | import ArrowUpLogo from "../images/arrow-up.svg" 23 | 24 | const Layout = ({ children, color }) => { 25 | const colorPallete = generateColorPallete(color) 26 | console.log(colorPallete.color.toString(), colorPallete.darkenedColor.toString(), colorPallete.lightenedColor.toString()) 27 | const foregroundColor = (colorPallete.color.isLight()) ? "black" : "white" 28 | const styleObject = { 29 | backgroundImage: `linear-gradient(${colorPallete.color.toString()}, ${colorPallete.darkenedColor.toString()})`, 30 | color: foregroundColor, 31 | borderTop: `30px dashed black`, 32 | borderBottom: `30px dashed black`, 33 | paddingBottom: "2em", 34 | backgroundAttachment: "fixed", 35 | } 36 | 37 | return (<> 38 |
39 |
40 |
41 |
{children}
42 |
43 |

What is Freebies Hunt?

44 |

45 | Freebies Hunt is a programmer-oriented and a personal resource list gathered by @{config.author.alias} after having a stroke with open content enthusiasm. 46 | This'll help you in finding quality free (and open source) resources to get started doing on your projects (or learnings). 47 |

48 |

49 | Freebies Hunt is also an open source website which you can see the source code for it right here. 50 | Of course, feedbacks and contributions are welcome! 🤗 51 |

52 |

Got API?

53 |

54 | If you are looking for an API of this site (for some reason) to get its data, you're in luck! It's available as an npm package. You could also see the source code of it in this remote repo. 55 |

56 |
57 | 58 |
59 |
60 |
61 | 62 | ) 63 | } 64 | 65 | Layout.defaultProps = { 66 | color: config.mainColor 67 | } 68 | 69 | Layout.propTypes = { 70 | children: PropTypes.node.isRequired, 71 | } 72 | 73 | export default Layout 74 | -------------------------------------------------------------------------------- /src/components/layout.scss: -------------------------------------------------------------------------------- 1 | html { 2 | font-family: sans-serif; 3 | -ms-text-size-adjust: 100%; 4 | -webkit-text-size-adjust: 100%; 5 | } 6 | body { 7 | margin: 0; 8 | scroll-behavior: smooth; 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | } 12 | article, 13 | aside, 14 | details, 15 | figcaption, 16 | figure, 17 | footer, 18 | header, 19 | main, 20 | menu, 21 | nav, 22 | section { 23 | display: block; 24 | } 25 | summary { 26 | cursor: pointer; 27 | } 28 | audio, 29 | canvas, 30 | progress, 31 | video { 32 | display: inline-block; 33 | } 34 | audio:not([controls]) { 35 | display: none; 36 | height: 0; 37 | } 38 | progress { 39 | vertical-align: baseline; 40 | } 41 | [hidden], 42 | template { 43 | display: none; 44 | } 45 | a { 46 | background-color: transparent; 47 | -webkit-text-decoration-skip: objects; 48 | color: currentColor; 49 | } 50 | a:active, 51 | a:hover { 52 | outline-width: 0; 53 | } 54 | abbr[title] { 55 | border-bottom: none; 56 | text-decoration: underline; 57 | text-decoration: underline dotted; 58 | } 59 | b, 60 | strong { 61 | font-weight: inherit; 62 | font-weight: bolder; 63 | } 64 | dfn { 65 | font-style: italic; 66 | } 67 | h1 { 68 | font-size: 2em; 69 | margin: 0.67em 0; 70 | } 71 | mark { 72 | background-color: #ff0; 73 | color: #000; 74 | } 75 | small { 76 | font-size: 80%; 77 | } 78 | sub, 79 | sup { 80 | font-size: 75%; 81 | line-height: 0; 82 | position: relative; 83 | vertical-align: baseline; 84 | } 85 | sub { 86 | bottom: -0.25em; 87 | } 88 | sup { 89 | top: -0.5em; 90 | } 91 | img { 92 | border-style: none; 93 | } 94 | svg { 95 | fill: currentColor; 96 | stroke: currentColor; 97 | width: 100%; 98 | height: 7em; 99 | } 100 | svg:not(:root) { 101 | overflow: hidden; 102 | } 103 | code, 104 | kbd, 105 | pre, 106 | samp { 107 | font-family: monospace, monospace; 108 | font-size: 1em; 109 | } 110 | figure { 111 | margin: 1em 40px; 112 | } 113 | hr { 114 | box-sizing: content-box; 115 | height: 0; 116 | overflow: visible; 117 | } 118 | button, 119 | input, 120 | optgroup, 121 | select, 122 | textarea { 123 | font: inherit; 124 | margin: 0; 125 | } 126 | optgroup { 127 | font-weight: 700; 128 | } 129 | button, 130 | input { 131 | overflow: visible; 132 | } 133 | button, 134 | select { 135 | text-transform: none; 136 | } 137 | [type="reset"], 138 | [type="submit"], 139 | button, 140 | html [type="button"] { 141 | -webkit-appearance: button; 142 | } 143 | [type="button"]::-moz-focus-inner, 144 | [type="reset"]::-moz-focus-inner, 145 | [type="submit"]::-moz-focus-inner, 146 | button::-moz-focus-inner { 147 | border-style: none; 148 | padding: 0; 149 | } 150 | [type="button"]:-moz-focusring, 151 | [type="reset"]:-moz-focusring, 152 | [type="submit"]:-moz-focusring, 153 | button:-moz-focusring { 154 | outline: 1px dotted ButtonText; 155 | } 156 | fieldset { 157 | border: 1px solid silver; 158 | margin: 0 2px; 159 | padding: 0.35em 0.625em 0.75em; 160 | } 161 | legend { 162 | box-sizing: border-box; 163 | color: inherit; 164 | display: table; 165 | max-width: 100%; 166 | padding: 0; 167 | white-space: normal; 168 | } 169 | textarea { 170 | overflow: auto; 171 | } 172 | [type="checkbox"], 173 | [type="radio"] { 174 | box-sizing: border-box; 175 | padding: 0; 176 | } 177 | [type="number"]::-webkit-inner-spin-button, 178 | [type="number"]::-webkit-outer-spin-button { 179 | height: auto; 180 | } 181 | [type="search"] { 182 | -webkit-appearance: textfield; 183 | outline-offset: -2px; 184 | } 185 | [type="search"]::-webkit-search-cancel-button, 186 | [type="search"]::-webkit-search-decoration { 187 | -webkit-appearance: none; 188 | } 189 | ::-webkit-input-placeholder { 190 | color: inherit; 191 | opacity: 0.54; 192 | } 193 | ::-webkit-file-upload-button { 194 | -webkit-appearance: button; 195 | font: inherit; 196 | } 197 | html { 198 | font: 112.5%/1.45em georgia, serif; 199 | box-sizing: border-box; 200 | overflow-y: scroll; 201 | } 202 | * { 203 | box-sizing: inherit; 204 | } 205 | *:before { 206 | box-sizing: inherit; 207 | } 208 | *:after { 209 | box-sizing: inherit; 210 | } 211 | body { 212 | color: hsla(0, 0%, 0%, 0.8); 213 | font-family: georgia, serif; 214 | font-weight: normal; 215 | word-wrap: break-word; 216 | font-kerning: normal; 217 | -moz-font-feature-settings: "kern", "liga", "clig", "calt"; 218 | -ms-font-feature-settings: "kern", "liga", "clig", "calt"; 219 | -webkit-font-feature-settings: "kern", "liga", "clig", "calt"; 220 | font-feature-settings: "kern", "liga", "clig", "calt"; 221 | } 222 | img { 223 | max-width: 100%; 224 | margin-left: 0; 225 | margin-right: 0; 226 | margin-top: 0; 227 | padding-bottom: 0; 228 | padding-left: 0; 229 | padding-right: 0; 230 | padding-top: 0; 231 | margin-bottom: 1.45rem; 232 | } 233 | h1 { 234 | margin-left: 0; 235 | margin-right: 0; 236 | margin-top: 0; 237 | padding-bottom: 0; 238 | padding-left: 0; 239 | padding-right: 0; 240 | padding-top: 0; 241 | margin-bottom: 1.45rem; 242 | color: inherit; 243 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 244 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 245 | font-weight: bold; 246 | text-rendering: optimizeLegibility; 247 | font-size: 2.25rem; 248 | line-height: 1.1; 249 | } 250 | h2 { 251 | margin-left: 0; 252 | margin-right: 0; 253 | margin-top: 0; 254 | padding-bottom: 0; 255 | padding-left: 0; 256 | padding-right: 0; 257 | padding-top: 0; 258 | margin-bottom: 1.45rem; 259 | color: inherit; 260 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 261 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 262 | font-weight: bold; 263 | text-rendering: optimizeLegibility; 264 | font-size: 1.62671rem; 265 | line-height: 1.1; 266 | } 267 | h3 { 268 | margin-left: 0; 269 | margin-right: 0; 270 | margin-top: 0; 271 | padding-bottom: 0; 272 | padding-left: 0; 273 | padding-right: 0; 274 | padding-top: 0; 275 | margin-bottom: 1.45rem; 276 | color: inherit; 277 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 278 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 279 | font-weight: bold; 280 | text-rendering: optimizeLegibility; 281 | font-size: 1.38316rem; 282 | line-height: 1.1; 283 | } 284 | h4 { 285 | margin-left: 0; 286 | margin-right: 0; 287 | margin-top: 0; 288 | padding-bottom: 0; 289 | padding-left: 0; 290 | padding-right: 0; 291 | padding-top: 0; 292 | margin-bottom: 1.45rem; 293 | color: inherit; 294 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 295 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 296 | font-weight: bold; 297 | text-rendering: optimizeLegibility; 298 | font-size: 1rem; 299 | line-height: 1.1; 300 | } 301 | h5 { 302 | margin-left: 0; 303 | margin-right: 0; 304 | margin-top: 0; 305 | padding-bottom: 0; 306 | padding-left: 0; 307 | padding-right: 0; 308 | padding-top: 0; 309 | margin-bottom: 1.45rem; 310 | color: inherit; 311 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 312 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 313 | font-weight: bold; 314 | text-rendering: optimizeLegibility; 315 | font-size: 0.85028rem; 316 | line-height: 1.1; 317 | } 318 | h6 { 319 | margin-left: 0; 320 | margin-right: 0; 321 | margin-top: 0; 322 | padding-bottom: 0; 323 | padding-left: 0; 324 | padding-right: 0; 325 | padding-top: 0; 326 | margin-bottom: 1.45rem; 327 | color: inherit; 328 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 329 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 330 | font-weight: bold; 331 | text-rendering: optimizeLegibility; 332 | font-size: 0.78405rem; 333 | line-height: 1.1; 334 | } 335 | hgroup { 336 | margin-left: 0; 337 | margin-right: 0; 338 | margin-top: 0; 339 | padding-bottom: 0; 340 | padding-left: 0; 341 | padding-right: 0; 342 | padding-top: 0; 343 | margin-bottom: 1.45rem; 344 | } 345 | ul { 346 | margin-left: 1.45rem; 347 | margin-right: 0; 348 | margin-top: 0; 349 | padding-bottom: 0; 350 | padding-left: 0; 351 | padding-right: 0; 352 | padding-top: 0; 353 | margin-bottom: 1.45rem; 354 | list-style-position: outside; 355 | list-style-image: none; 356 | } 357 | ol { 358 | margin-left: 1.45rem; 359 | margin-right: 0; 360 | margin-top: 0; 361 | padding-bottom: 0; 362 | padding-left: 0; 363 | padding-right: 0; 364 | padding-top: 0; 365 | margin-bottom: 1.45rem; 366 | list-style-position: outside; 367 | list-style-image: none; 368 | } 369 | dl { 370 | margin-left: 0; 371 | margin-right: 0; 372 | margin-top: 0; 373 | padding-bottom: 0; 374 | padding-left: 0; 375 | padding-right: 0; 376 | padding-top: 0; 377 | margin-bottom: 1.45rem; 378 | } 379 | dd { 380 | margin-left: 0; 381 | margin-right: 0; 382 | margin-top: 0; 383 | padding-bottom: 0; 384 | padding-left: 0; 385 | padding-right: 0; 386 | padding-top: 0; 387 | margin-bottom: 1.45rem; 388 | } 389 | p { 390 | margin-left: 0; 391 | margin-right: 0; 392 | margin-top: 0; 393 | padding-bottom: 0; 394 | padding-left: 0; 395 | padding-right: 0; 396 | padding-top: 0; 397 | margin-bottom: 1.45rem; 398 | } 399 | figure { 400 | margin-left: 0; 401 | margin-right: 0; 402 | margin-top: 0; 403 | padding-bottom: 0; 404 | padding-left: 0; 405 | padding-right: 0; 406 | padding-top: 0; 407 | margin-bottom: 1.45rem; 408 | } 409 | pre { 410 | margin-left: 0; 411 | margin-right: 0; 412 | margin-top: 0; 413 | margin-bottom: 1.45rem; 414 | font-size: 0.85rem; 415 | line-height: 1.42; 416 | background: hsla(0, 0%, 0%, 0.04); 417 | border-radius: 3px; 418 | overflow: auto; 419 | word-wrap: normal; 420 | padding: 1.45rem; 421 | } 422 | table { 423 | margin-left: 0; 424 | margin-right: 0; 425 | margin-top: 0; 426 | padding-bottom: 0; 427 | padding-left: 0; 428 | padding-right: 0; 429 | padding-top: 0; 430 | margin-bottom: 1.45rem; 431 | font-size: 1rem; 432 | line-height: 1.45rem; 433 | border-collapse: collapse; 434 | width: 100%; 435 | } 436 | fieldset { 437 | margin-left: 0; 438 | margin-right: 0; 439 | margin-top: 0; 440 | padding-bottom: 0; 441 | padding-left: 0; 442 | padding-right: 0; 443 | padding-top: 0; 444 | margin-bottom: 1.45rem; 445 | } 446 | blockquote { 447 | margin-left: 1.45rem; 448 | margin-right: 1.45rem; 449 | margin-top: 0; 450 | padding-bottom: 0; 451 | padding-left: 0; 452 | padding-right: 0; 453 | padding-top: 0; 454 | margin-bottom: 1.45rem; 455 | } 456 | form { 457 | margin-left: 0; 458 | margin-right: 0; 459 | margin-top: 0; 460 | padding-bottom: 0; 461 | padding-left: 0; 462 | padding-right: 0; 463 | padding-top: 0; 464 | margin-bottom: 1.45rem; 465 | } 466 | noscript { 467 | margin-left: 0; 468 | margin-right: 0; 469 | margin-top: 0; 470 | padding-bottom: 0; 471 | padding-left: 0; 472 | padding-right: 0; 473 | padding-top: 0; 474 | margin-bottom: 1.45rem; 475 | } 476 | iframe { 477 | margin-left: 0; 478 | margin-right: 0; 479 | margin-top: 0; 480 | padding-bottom: 0; 481 | padding-left: 0; 482 | padding-right: 0; 483 | padding-top: 0; 484 | margin-bottom: 1.45rem; 485 | } 486 | hr { 487 | margin-left: 0; 488 | margin-right: 0; 489 | margin-top: 0; 490 | padding-bottom: 0; 491 | padding-left: 0; 492 | padding-right: 0; 493 | padding-top: 0; 494 | margin-bottom: calc(1.45rem - 1px); 495 | height: 5px; 496 | } 497 | address { 498 | margin-left: 0; 499 | margin-right: 0; 500 | margin-top: 0; 501 | padding-bottom: 0; 502 | padding-left: 0; 503 | padding-right: 0; 504 | padding-top: 0; 505 | margin-bottom: 1.45rem; 506 | } 507 | b { 508 | font-weight: bold; 509 | } 510 | strong { 511 | font-weight: bold; 512 | } 513 | dt { 514 | font-weight: bold; 515 | } 516 | th { 517 | font-weight: bold; 518 | } 519 | li { 520 | margin-bottom: calc(1.45rem / 2); 521 | } 522 | ol li { 523 | padding-left: 0; 524 | } 525 | ul li { 526 | padding-left: 0; 527 | } 528 | li > ol { 529 | margin-left: 1.45rem; 530 | margin-bottom: calc(1.45rem / 2); 531 | margin-top: calc(1.45rem / 2); 532 | } 533 | li > ul { 534 | margin-left: 1.45rem; 535 | margin-bottom: calc(1.45rem / 2); 536 | margin-top: calc(1.45rem / 2); 537 | } 538 | blockquote *:last-child { 539 | margin-bottom: 0; 540 | } 541 | li *:last-child { 542 | margin-bottom: 0; 543 | } 544 | p *:last-child { 545 | margin-bottom: 0; 546 | } 547 | li > p { 548 | margin-bottom: calc(1.45rem / 2); 549 | } 550 | code { 551 | font-size: 0.85rem; 552 | line-height: 1.45rem; 553 | } 554 | kbd { 555 | font-size: 0.85rem; 556 | line-height: 1.45rem; 557 | } 558 | samp { 559 | font-size: 0.85rem; 560 | line-height: 1.45rem; 561 | } 562 | abbr { 563 | border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5); 564 | cursor: help; 565 | } 566 | acronym { 567 | border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5); 568 | cursor: help; 569 | } 570 | abbr[title] { 571 | border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5); 572 | cursor: help; 573 | text-decoration: none; 574 | } 575 | thead { 576 | text-align: left; 577 | } 578 | td, 579 | th { 580 | text-align: left; 581 | border-bottom: 1px solid hsla(0, 0%, 0%, 0.12); 582 | font-feature-settings: "tnum"; 583 | -moz-font-feature-settings: "tnum"; 584 | -ms-font-feature-settings: "tnum"; 585 | -webkit-font-feature-settings: "tnum"; 586 | padding-left: 0.96667rem; 587 | padding-right: 0.96667rem; 588 | padding-top: 0.725rem; 589 | padding-bottom: calc(0.725rem - 1px); 590 | } 591 | th:first-child, 592 | td:first-child { 593 | padding-left: 0; 594 | } 595 | th:last-child, 596 | td:last-child { 597 | padding-right: 0; 598 | } 599 | tt, 600 | code { 601 | background-color: hsla(0, 0%, 0%, 0.04); 602 | border-radius: 3px; 603 | font-family: "SFMono-Regular", Consolas, "Roboto Mono", "Droid Sans Mono", 604 | "Liberation Mono", Menlo, Courier, monospace; 605 | padding: 0; 606 | padding-top: 0.2em; 607 | padding-bottom: 0.2em; 608 | } 609 | pre code { 610 | background: none; 611 | line-height: 1.42; 612 | } 613 | code:before, 614 | code:after, 615 | tt:before, 616 | tt:after { 617 | letter-spacing: -0.2em; 618 | content: " "; 619 | } 620 | pre code:before, 621 | pre code:after, 622 | pre tt:before, 623 | pre tt:after { 624 | content: ""; 625 | } 626 | @media only screen and (max-width: 600px) { 627 | html { 628 | font-size: 100%; 629 | } 630 | } 631 | 632 | input:focus { 633 | background: rgba(0, 0, 0, 0.2); 634 | color: white; 635 | } 636 | 637 | /* custom classes */ 638 | main { 639 | padding-top: 4em; 640 | 641 | @media only screen and (min-width: 600px) { 642 | padding-left: 2em; 643 | padding-right: 2em; 644 | } 645 | } 646 | 647 | .content-wrapper { 648 | margin: 0 auto; 649 | max-width: 960px; 650 | padding: 0rem 0.5rem 1.45rem; 651 | 652 | @media only screen and (min-width: 600px) { 653 | padding: 0px 1.0875rem 1.45rem; 654 | } 655 | } 656 | 657 | .about { 658 | margin: 2em auto; 659 | text-align: center; 660 | } 661 | 662 | .go-to-top { 663 | $size: 4em; 664 | $bg: rgba(0, 0, 0, 0.2); 665 | align-items: center; 666 | background: $bg; 667 | border-radius: 9999px; 668 | bottom: 0; 669 | color: white; 670 | display: flex; 671 | height: $size; 672 | justify-content: center; 673 | margin: 1em; 674 | position: fixed; 675 | right: 0; 676 | width: $size; 677 | 678 | &:hover { 679 | background: darken($bg, 20%); 680 | } 681 | 682 | svg { 683 | $icon-size: $size / 2; 684 | height: $icon-size; 685 | } 686 | } 687 | -------------------------------------------------------------------------------- /src/components/seo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SEO component that queries for data with 3 | * Gatsby's useStaticQuery React hook 4 | * 5 | * See: https://www.gatsbyjs.org/docs/use-static-query/ 6 | */ 7 | 8 | import React from "react" 9 | import PropTypes from "prop-types" 10 | import Helmet from "react-helmet" 11 | 12 | import config from "../config" 13 | 14 | function SEO({ description, lang, meta, keywords, title }) { 15 | const metaDescription = description || config.description 16 | return ( 17 | 0 59 | ? { 60 | name: `keywords`, 61 | content: keywords.join(`, `), 62 | } 63 | : [] 64 | ) 65 | .concat(meta)} 66 | /> 67 | ) 68 | } 69 | 70 | SEO.defaultProps = { 71 | lang: `en`, 72 | meta: [], 73 | keywords: [], 74 | } 75 | 76 | SEO.propTypes = { 77 | description: PropTypes.string, 78 | lang: PropTypes.string, 79 | meta: PropTypes.array, 80 | keywords: PropTypes.arrayOf(PropTypes.string), 81 | title: PropTypes.string.isRequired, 82 | } 83 | 84 | export default SEO 85 | -------------------------------------------------------------------------------- /src/components/textInput.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const textInput = ({ description, className, style, value }) => { 4 | return ( 5 |
6 | { description } 7 | 8 |
9 | ) 10 | } 11 | 12 | export default textInput; 13 | -------------------------------------------------------------------------------- /src/components/userLinks.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import Icon from "./icon" 3 | 4 | import config from "../config" 5 | 6 | import "./userLinks.scss" 7 | 8 | const userLinks = ({ userLinks }) => ( 9 |
10 |

Want to see more of my projects?

11 |
12 | {Object.entries(userLinks).map(userLink => { 13 | const name = userLink[0]; 14 | const link = userLink[1]; 15 | 16 | return 17 | })} 18 |
19 |
20 | ) 21 | 22 | userLinks.defaultProps = { 23 | userLinks: config.socialLinks 24 | } 25 | 26 | export default userLinks; 27 | -------------------------------------------------------------------------------- /src/components/userLinks.scss: -------------------------------------------------------------------------------- 1 | address { 2 | text-align: center; 3 | 4 | h3 {margin: 0;} 5 | 6 | &::before { 7 | border-top: 3px double currentColor; 8 | content: ''; 9 | display: block; 10 | margin-bottom: 2em; 11 | } 12 | } 13 | 14 | .social-links { 15 | align-items: center; 16 | display: flex; 17 | justify-content: center; 18 | } 19 | 20 | a.social-link { 21 | margin: 1em; 22 | padding: 1em; 23 | 24 | svg { 25 | $size: 2em; 26 | height: $size; 27 | width: $size; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | title: `Freebies Hunt`, 3 | description: `It's a personal list of digital freebies that can be found on the web.`, 4 | googleAnalyticsId: `UA-126718538-4`, 5 | author: { 6 | name: `Gabriel Arazas`, 7 | alias: `foo-dogsquared` 8 | }, 9 | socialLinks: { 10 | github: `https://github.com/foo-dogsquared/`, 11 | twitter: `https://twitter.com/foo_dogsquared`, 12 | email: `mailto:foo.dogsquared@gmail.com` 13 | }, 14 | repoLink: `https://github.com/foo-dogsquared/freebies-hunt`, 15 | api: { 16 | link: `https://www.npmjs.com/package/freebies-hunt-api`, 17 | repo: `https://github.com/foo-dogsquared/freebies-hunt-api`, 18 | }, 19 | license: { 20 | name: "GNU General Public License v3.0", 21 | link: "https://choosealicense.com/licenses/gpl-3.0/" 22 | }, 23 | mainSite: `https://foo-dogsquared.github.io`, 24 | mainColor: "#d59783", 25 | } 26 | -------------------------------------------------------------------------------- /src/iconExtract.js: -------------------------------------------------------------------------------- 1 | const svgSprite = require("svg-sprite"); 2 | const fs = require("fs"); 3 | const path = require("path"); 4 | 5 | // the main images directory 6 | const imagesDir = path.resolve(__dirname, "images/"); 7 | 8 | const outputDir = path.resolve(__dirname, "../static"); 9 | 10 | // the list of folders inside of the image directory 11 | const directories = ["category-icons", "social-icons"]; 12 | 13 | for (const directory of directories) { 14 | const currentDir = path.resolve(imagesDir, directory); 15 | const svgconfig = { 16 | dest: path.resolve(__dirname, "../static"), 17 | mode: { 18 | symbol: true, 19 | } 20 | } 21 | 22 | const svgSpriteInstance = new svgSprite(svgconfig) 23 | const files = fs.readdirSync(currentDir, { withFileTypes: true }); 24 | 25 | for (const file of files) { 26 | if (path.extname(file.name) !== '.svg') continue; 27 | const name = path.basename(file.name, ".svg"); 28 | const filePath = path.resolve(currentDir, file.name); 29 | 30 | const svgFile = fs.readFileSync(filePath, "utf8"); 31 | console.log(`Extracting at ${filePath}`); 32 | svgSpriteInstance.add(filePath, file.name, svgFile); 33 | } 34 | 35 | svgSpriteInstance.compile((error, result) => { 36 | for (const type in result) { 37 | for (const resource in result[type]) { 38 | fs.writeFile(path.resolve(outputDir, `${directory}.svg`), result[type][resource].contents, (error) => { 39 | if (error) throw error; 40 | }) 41 | } 42 | } 43 | }) 44 | 45 | console.log("Moving on to the next directory..."); 46 | } -------------------------------------------------------------------------------- /src/images/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/foo-dogsquared/freebies-hunt/87585bc6ef056b81613bbcece23b4d6fbfddb637/src/images/404.png -------------------------------------------------------------------------------- /src/images/arrow-up.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/images/category-icons/algorithm.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/images/category-icons/articles.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 9 | 11 | 13 | 15 | 28 | 30 | 32 | 34 | 37 | 39 | 41 | 43 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /src/images/category-icons/audio.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/images/category-icons/blog.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 12 | 13 | 14 | 15 | 16 | 21 | 22 | 23 | 24 | 25 | 33 | 34 | 35 | 36 | 37 | 39 | 40 | 41 | 42 | 43 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /src/images/category-icons/cad.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/images/category-icons/cdn.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/images/category-icons/clock.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/images/category-icons/communication.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/images/category-icons/community.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | 27 | 52 | 53 | 54 | 55 | 56 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /src/images/category-icons/courses.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 10 | 11 | 12 | 13 | 14 | 36 | 37 | 38 | 39 | 40 | 43 | 44 | 45 | 46 | 47 | 49 | 50 | 51 | 52 | 53 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /src/images/category-icons/documentation.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | 17 | 18 | 19 | 20 | 21 | 24 | 25 | 26 | 27 | 28 | 31 | 32 | 33 | 34 | 35 | 39 | 40 | 41 | 42 | 43 | 45 | 46 | 47 | 48 | 49 | 51 | 52 | 53 | 54 | 55 | 57 | 58 | 59 | 60 | 61 | 64 | 65 | 66 | 67 | 68 | 70 | 71 | 72 | 73 | 74 | 76 | 77 | 78 | 79 | 80 | 82 | 83 | 84 | 85 | 86 | 88 | 89 | 90 | 91 | 92 | 94 | 95 | 96 | 97 | 98 | 100 | 101 | 102 | 103 | 104 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /src/images/category-icons/game-controller.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 15 | 16 | 17 | 19 | 20 | 21 | 24 | 25 | 26 | 27 | 31 | 32 | 33 | 34 | 35 | 37 | 38 | 39 | 41 | 42 | 43 | 44 | 45 | 50 | 51 | 52 | 56 | 57 | 58 | 64 | 65 | 66 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /src/images/category-icons/games.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 14 | 15 | 16 | 17 | 18 | 24 | 25 | 26 | 27 | 28 | 36 | 37 | 38 | 39 | 40 | 43 | 44 | 45 | 46 | 47 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /src/images/category-icons/hardware.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 32 | 33 | 34 | 35 | 36 | 64 | 65 | 66 | 67 | 68 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /src/images/category-icons/ide.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/images/category-icons/inspiration.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 24 | 25 | 26 | 27 | 28 | 30 | 31 | 32 | 33 | 34 | 36 | 37 | 38 | 39 | 40 | 43 | 44 | 45 | 46 | 47 | 50 | 51 | 52 | 53 | 54 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /src/images/category-icons/list.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/images/category-icons/math.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/images/category-icons/music-note.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/images/category-icons/newsletter.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/images/category-icons/oer.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/images/category-icons/online-learning.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/images/category-icons/open-content.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/images/category-icons/operating-system.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | 15 | 20 | 21 | 22 | 23 | 24 | 27 | 28 | 29 | 30 | 31 | 34 | 35 | 36 | 37 | 38 | 48 | 49 | 50 | 51 | 52 | 56 | 57 | 58 | 59 | 60 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /src/images/category-icons/paintbrush.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | 15 | 16 | 17 | 18 | 19 | 21 | 22 | 23 | 24 | 25 | 27 | 28 | 29 | 30 | 31 | 34 | 35 | 36 | 37 | 38 | 59 | 60 | 61 | 62 | 63 | 66 | 67 | 68 | 69 | 70 | 73 | 74 | 75 | 76 | 77 | 79 | 80 | 81 | 82 | 83 | 86 | 87 | 88 | 89 | 90 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /src/images/category-icons/platform.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 10 | 12 | 14 | 16 | 23 | 25 | 27 | 29 | 31 | 33 | 35 | 37 | 39 | 41 | 43 | 45 | 47 | 49 | 51 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /src/images/category-icons/standards.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/images/category-icons/stocks.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 11 | 14 | 16 | 22 | 57 | 63 | 66 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /src/images/category-icons/student-packs.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/images/category-icons/study.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 13 | 14 | 15 | 16 | 17 | 20 | 21 | 22 | 23 | 24 | 29 | 30 | 31 | 32 | 33 | 46 | 47 | 48 | 49 | 50 | 52 | 53 | 54 | 55 | 56 | 59 | 60 | 61 | 62 | 63 | 65 | 66 | 67 | 68 | 69 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /src/images/category-icons/textbook.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/images/category-icons/uncategorized.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 17 | 18 | 19 | 20 | 21 | 24 | 25 | 26 | 27 | 28 | 31 | 32 | 33 | 34 | 35 | 43 | 44 | 45 | 46 | 47 | 53 | 54 | 55 | 56 | 57 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /src/images/category-icons/web-browsers.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/images/category-icons/youtube.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/images/freebies-hunt-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/images/social-icons/email.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/images/social-icons/github.svg: -------------------------------------------------------------------------------- 1 | GitHub icon -------------------------------------------------------------------------------- /src/images/social-icons/twitter.svg: -------------------------------------------------------------------------------- 1 | Twitter icon -------------------------------------------------------------------------------- /src/scripts.js: -------------------------------------------------------------------------------- 1 | const tinycolor = require("tinycolor2"); 2 | 3 | module.exports.kebabCase = (word) => { 4 | const nonAlphaNumericRegex = /[^A-Za-z0-9]/gi; 5 | const whitespace = /\s+/gi; 6 | return word.split(whitespace).map(word => word.replace(nonAlphaNumericRegex, "").toLowerCase()).join("-"); 7 | } 8 | 9 | module.exports.generateColorPallete = (color, { brightenPercentage = 10, darkenPercentage = 10 } = {}) => { 10 | const tinycolorInstance = tinycolor(color); 11 | const brightenedColor = tinycolorInstance.clone().brighten(brightenPercentage) 12 | const darkenedColor = tinycolorInstance.clone().darken(darkenPercentage) 13 | return { 14 | "color": tinycolorInstance, 15 | "lightenedColor": brightenedColor, 16 | "darkenedColor": darkenedColor, 17 | } 18 | } 19 | 20 | module.exports.generateStyleObject = (color) => { 21 | const tinycolorInstance = tinycolor(color); 22 | const lightenedColor = tinycolorInstance.brighten(20); 23 | 24 | return { 25 | backgroundImage: `linear-gradient(${color}, ${lightenedColor})`, 26 | color: (tinycolorInstance.isLight()) ? "black" : "white", 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/templates/categories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import SEO from "../components/seo" 3 | import CategorySearchGrid from "../components/categoryGridSearchEngine"; 4 | 5 | import Layout from "../components/layout" 6 | 7 | export default ({ pageContext: { categories } }) => ( 8 | 9 | 10 |

Hello, fellow freebie hunters 👋

11 |

Welcome to my personal freebies hunting list. Take a look around and see a glimpse of the world of open content (and free stuff) just laying around in the interwebs.

12 | 13 |
14 | ); 15 | -------------------------------------------------------------------------------- /src/templates/category.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'gatsby'; 3 | 4 | // npm scripts 5 | import marked from "marked"; 6 | 7 | // custom scripts 8 | import { kebabCase } from "../scripts" 9 | 10 | // components 11 | import SEO from '../components/seo' 12 | import Layout from '../components/layout'; 13 | import Icon from "../components/icon"; 14 | import CategoryGrid from "../components/categoryGrid" 15 | import FreebieSearchInterface from "../components/freebieSearchInterface" 16 | 17 | import "./category.scss"; 18 | 19 | export default ({ pageContext: { name, category, categories } }) => { 20 | const recommendedCategories = {}; 21 | const categorySet = Object.keys(categories).sort(); 22 | while (Object.keys(recommendedCategories).length <= 2) { 23 | const randomCategory = categorySet[Math.floor(Math.random() * categorySet.length)]; 24 | if (randomCategory === name) continue; 25 | 26 | recommendedCategories[randomCategory] = categories[randomCategory]; 27 | } 28 | 29 | return ( 30 | 31 | 32 | 33 |
34 |

{ name }

35 |

36 |
37 | 38 | 39 |
40 |

Some other categories:

41 | 42 |
43 | 44 |
45 | Categories index: 46 | 53 |
54 |
55 | ) 56 | } 57 | -------------------------------------------------------------------------------- /src/templates/category.scss: -------------------------------------------------------------------------------- 1 | .category-info { 2 | text-align: center; 3 | margin-bottom: 2em; 4 | 5 | > * {margin: .5em auto;} 6 | } 7 | 8 | .other-categories { 9 | margin: 2em auto; 10 | 11 | h3 {margin: 0;} 12 | } 13 | 14 | .categories-index { 15 | margin: 2em auto; 16 | color: black; 17 | 18 | summary { 19 | cursor: pointer; 20 | background: white; 21 | font-size: 1em; 22 | font-weight: bolder; 23 | padding: 1em; 24 | text-decoration: underline; 25 | } 26 | 27 | nav { 28 | background: white; 29 | border: 2px solid currentColor; 30 | display: flex; 31 | flex-flow: row wrap; 32 | justify-content: space-evenly; 33 | text-align: center; 34 | 35 | & > * {margin: .5em;} 36 | 37 | b {font-size: 1.25em;} 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /static/freebies-hunt-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /static/social-icons.svg: -------------------------------------------------------------------------------- 1 | --------------------------------------------------------------------------------