├── .eslintrc
├── src
├── images
│ ├── bmc.png
│ ├── mf.jpeg
│ ├── ghibli-mf.png
│ ├── mnfx-screens.jpeg
│ ├── logo
│ │ ├── mnfx-icon.png
│ │ └── mnfx-icon.svg
│ ├── favicon
│ │ └── mnfx-icon.png
│ ├── michaelfransman.jpeg
│ └── projects
│ │ ├── ads-logo.png
│ │ ├── bh-logo.png
│ │ ├── dsm-logo.png
│ │ ├── ep-logo.png
│ │ ├── eup-logo.png
│ │ ├── kir-logo.png
│ │ └── pz-logo.jpg
├── fonts
│ ├── Poppins-Bold.woff2
│ ├── Poppins-Thin.woff2
│ ├── Poppins-Black.woff2
│ ├── Poppins-Italic.woff2
│ ├── Poppins-Light.woff2
│ ├── Poppins-Medium.woff2
│ ├── Poppins-ExtraBold.woff2
│ ├── Poppins-Regular.woff2
│ ├── Poppins-SemiBold.woff2
│ ├── Poppins-BlackItalic.woff2
│ ├── Poppins-BoldItalic.woff2
│ ├── Poppins-ExtraLight.woff2
│ ├── Poppins-LightItalic.woff2
│ ├── Poppins-ThinItalic.woff2
│ ├── Poppins-MediumItalic.woff2
│ ├── Poppins-SemiBoldItalic.woff2
│ ├── Poppins-ExtraBoldItalic.woff2
│ ├── Poppins-ExtraLightItalic.woff2
│ └── WDXLLubrifontTC-Regular.woff2
├── styles
│ ├── customs
│ │ ├── _fonts.scss
│ │ ├── index.scss
│ │ ├── _variables.scss
│ │ ├── _functions.scss
│ │ ├── _utils.scss
│ │ ├── _colors.scss
│ │ ├── _keyframes.scss
│ │ ├── _mediaqueries.scss
│ │ └── _selectors.scss
│ ├── _globals.scss
│ ├── modules
│ │ ├── layout
│ │ │ ├── backdrop.module.scss
│ │ │ ├── hamburger.module.scss
│ │ │ ├── mobileMenu.module.scss
│ │ │ └── desktopMenu.module.scss
│ │ ├── forms
│ │ │ └── callForm.module.scss
│ │ ├── ui
│ │ │ ├── notes.module.scss
│ │ │ ├── spotify.module.scss
│ │ │ ├── maps.module.scss
│ │ │ ├── biography.module.scss
│ │ │ ├── usp.module.scss
│ │ │ ├── actual.module.scss
│ │ │ ├── faq.module.scss
│ │ │ ├── prices.module.scss
│ │ │ ├── info.module.scss
│ │ │ └── projects.module.scss
│ │ ├── templates
│ │ │ └── topic.module.scss
│ │ └── pages
│ │ │ ├── index.module.scss
│ │ │ ├── contact.module.scss
│ │ │ ├── prices.module.scss
│ │ │ ├── blog.module.scss
│ │ │ ├── about.module.scss
│ │ │ ├── topics.module.scss
│ │ │ ├── faq.module.scss
│ │ │ └── portfolio.module.scss
│ ├── _swiper.scss
│ ├── _cookie.scss
│ ├── resets.scss
│ ├── layout.scss
│ ├── _page.scss
│ └── _typography.scss
├── components
│ ├── navbar
│ │ ├── menuOverlay.jsx
│ │ ├── hamburger.jsx
│ │ └── mobileMenu.jsx
│ ├── google
│ │ ├── adsmulti.jsx
│ │ ├── adsdisp.jsx
│ │ └── maps.jsx
│ ├── ui
│ │ ├── notes.jsx
│ │ ├── spotify.jsx
│ │ ├── about.jsx
│ │ ├── biography.jsx
│ │ ├── usp.jsx
│ │ └── faq.jsx
│ ├── layout
│ │ ├── banner.jsx
│ │ └── hero.jsx
│ ├── helpers
│ │ └── responsiveTag.jsx
│ └── layout.jsx
├── context
│ ├── root-element.jsx
│ └── hydration-context.jsx
├── lib
│ └── wrapRoot.js
├── hooks
│ ├── use-site-metadata.jsx
│ └── use-translation.jsx
├── i18n
│ └── config.js
├── pages
│ ├── success.jsx
│ ├── 404.jsx
│ ├── privacybeleid.jsx
│ ├── algemene-voorwaarden.jsx
│ ├── over.jsx
│ ├── contact.jsx
│ ├── diensten
│ │ ├── analyse-laten-uitvoeren.jsx
│ │ ├── email-template-laten-maken.jsx
│ │ ├── website-laten-maken.jsx
│ │ ├── onderhoud-updates-uitvoeren.jsx
│ │ ├── zoekmachineoptimalisatie.jsx
│ │ ├── webshop-laten-maken.jsx
│ │ ├── webapplicatie-laten-maken.jsx
│ │ └── optimalisaties-laten-uitvoeren.jsx
│ ├── index.jsx
│ └── topics.jsx
└── utils
│ └── fetchLastCommitDate.js
├── static
├── favicon.ico
├── mnfx-favi.png
├── mnfx-scl.png
├── mnfx-screens.jpeg
├── MichaelFransman.jpeg
├── mnfx-prijslijst.pdf
├── project-images
│ ├── keeptreal.png
│ ├── blackharmony.png
│ ├── dsmelodies.png
│ ├── eternitydrum.png
│ ├── afrodiasphere.png
│ └── edutainuproductions.png
├── ads.txt
└── llms.txt
├── .github
├── FUNDING.yml
├── dependabot.yml
└── workflows
│ └── codeql-analysis.yml
├── gatsby-browser.js
├── .stylelintrc
├── .vscode
└── launch.json
├── SECURITY.md
├── gatsby-ssr.jsx
├── .gitignore
├── README.md
├── deno.lock
├── package.json
└── functions
└── sendmail
└── sendmail.js
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["airbnb"]
3 | }
4 |
--------------------------------------------------------------------------------
/src/images/bmc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeyfe6/menefex/HEAD/src/images/bmc.png
--------------------------------------------------------------------------------
/src/images/mf.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeyfe6/menefex/HEAD/src/images/mf.jpeg
--------------------------------------------------------------------------------
/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeyfe6/menefex/HEAD/static/favicon.ico
--------------------------------------------------------------------------------
/static/mnfx-favi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeyfe6/menefex/HEAD/static/mnfx-favi.png
--------------------------------------------------------------------------------
/static/mnfx-scl.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeyfe6/menefex/HEAD/static/mnfx-scl.png
--------------------------------------------------------------------------------
/src/images/ghibli-mf.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeyfe6/menefex/HEAD/src/images/ghibli-mf.png
--------------------------------------------------------------------------------
/static/mnfx-screens.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeyfe6/menefex/HEAD/static/mnfx-screens.jpeg
--------------------------------------------------------------------------------
/src/fonts/Poppins-Bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeyfe6/menefex/HEAD/src/fonts/Poppins-Bold.woff2
--------------------------------------------------------------------------------
/src/fonts/Poppins-Thin.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeyfe6/menefex/HEAD/src/fonts/Poppins-Thin.woff2
--------------------------------------------------------------------------------
/src/images/mnfx-screens.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeyfe6/menefex/HEAD/src/images/mnfx-screens.jpeg
--------------------------------------------------------------------------------
/static/MichaelFransman.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeyfe6/menefex/HEAD/static/MichaelFransman.jpeg
--------------------------------------------------------------------------------
/static/mnfx-prijslijst.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeyfe6/menefex/HEAD/static/mnfx-prijslijst.pdf
--------------------------------------------------------------------------------
/src/fonts/Poppins-Black.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeyfe6/menefex/HEAD/src/fonts/Poppins-Black.woff2
--------------------------------------------------------------------------------
/src/fonts/Poppins-Italic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeyfe6/menefex/HEAD/src/fonts/Poppins-Italic.woff2
--------------------------------------------------------------------------------
/src/fonts/Poppins-Light.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeyfe6/menefex/HEAD/src/fonts/Poppins-Light.woff2
--------------------------------------------------------------------------------
/src/fonts/Poppins-Medium.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeyfe6/menefex/HEAD/src/fonts/Poppins-Medium.woff2
--------------------------------------------------------------------------------
/src/images/logo/mnfx-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeyfe6/menefex/HEAD/src/images/logo/mnfx-icon.png
--------------------------------------------------------------------------------
/src/fonts/Poppins-ExtraBold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeyfe6/menefex/HEAD/src/fonts/Poppins-ExtraBold.woff2
--------------------------------------------------------------------------------
/src/fonts/Poppins-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeyfe6/menefex/HEAD/src/fonts/Poppins-Regular.woff2
--------------------------------------------------------------------------------
/src/fonts/Poppins-SemiBold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeyfe6/menefex/HEAD/src/fonts/Poppins-SemiBold.woff2
--------------------------------------------------------------------------------
/src/images/favicon/mnfx-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeyfe6/menefex/HEAD/src/images/favicon/mnfx-icon.png
--------------------------------------------------------------------------------
/src/images/michaelfransman.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeyfe6/menefex/HEAD/src/images/michaelfransman.jpeg
--------------------------------------------------------------------------------
/src/images/projects/ads-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeyfe6/menefex/HEAD/src/images/projects/ads-logo.png
--------------------------------------------------------------------------------
/src/images/projects/bh-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeyfe6/menefex/HEAD/src/images/projects/bh-logo.png
--------------------------------------------------------------------------------
/src/images/projects/dsm-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeyfe6/menefex/HEAD/src/images/projects/dsm-logo.png
--------------------------------------------------------------------------------
/src/images/projects/ep-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeyfe6/menefex/HEAD/src/images/projects/ep-logo.png
--------------------------------------------------------------------------------
/src/images/projects/eup-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeyfe6/menefex/HEAD/src/images/projects/eup-logo.png
--------------------------------------------------------------------------------
/src/images/projects/kir-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeyfe6/menefex/HEAD/src/images/projects/kir-logo.png
--------------------------------------------------------------------------------
/src/images/projects/pz-logo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeyfe6/menefex/HEAD/src/images/projects/pz-logo.jpg
--------------------------------------------------------------------------------
/src/fonts/Poppins-BlackItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeyfe6/menefex/HEAD/src/fonts/Poppins-BlackItalic.woff2
--------------------------------------------------------------------------------
/src/fonts/Poppins-BoldItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeyfe6/menefex/HEAD/src/fonts/Poppins-BoldItalic.woff2
--------------------------------------------------------------------------------
/src/fonts/Poppins-ExtraLight.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeyfe6/menefex/HEAD/src/fonts/Poppins-ExtraLight.woff2
--------------------------------------------------------------------------------
/src/fonts/Poppins-LightItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeyfe6/menefex/HEAD/src/fonts/Poppins-LightItalic.woff2
--------------------------------------------------------------------------------
/src/fonts/Poppins-ThinItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeyfe6/menefex/HEAD/src/fonts/Poppins-ThinItalic.woff2
--------------------------------------------------------------------------------
/src/styles/customs/_fonts.scss:
--------------------------------------------------------------------------------
1 | $poppins-font: "Poppins", sans-serif;
2 | $wdxl-font: "WDXL Lubrifont TC", sans-serif;
3 |
--------------------------------------------------------------------------------
/static/project-images/keeptreal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeyfe6/menefex/HEAD/static/project-images/keeptreal.png
--------------------------------------------------------------------------------
/src/fonts/Poppins-MediumItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeyfe6/menefex/HEAD/src/fonts/Poppins-MediumItalic.woff2
--------------------------------------------------------------------------------
/src/fonts/Poppins-SemiBoldItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeyfe6/menefex/HEAD/src/fonts/Poppins-SemiBoldItalic.woff2
--------------------------------------------------------------------------------
/static/project-images/blackharmony.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeyfe6/menefex/HEAD/static/project-images/blackharmony.png
--------------------------------------------------------------------------------
/static/project-images/dsmelodies.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeyfe6/menefex/HEAD/static/project-images/dsmelodies.png
--------------------------------------------------------------------------------
/static/project-images/eternitydrum.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeyfe6/menefex/HEAD/static/project-images/eternitydrum.png
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | # patreon: menefexWMB
4 | buy_me_a_coffee: menefex
5 |
--------------------------------------------------------------------------------
/src/fonts/Poppins-ExtraBoldItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeyfe6/menefex/HEAD/src/fonts/Poppins-ExtraBoldItalic.woff2
--------------------------------------------------------------------------------
/src/fonts/Poppins-ExtraLightItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeyfe6/menefex/HEAD/src/fonts/Poppins-ExtraLightItalic.woff2
--------------------------------------------------------------------------------
/src/fonts/WDXLLubrifontTC-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeyfe6/menefex/HEAD/src/fonts/WDXLLubrifontTC-Regular.woff2
--------------------------------------------------------------------------------
/static/project-images/afrodiasphere.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeyfe6/menefex/HEAD/static/project-images/afrodiasphere.png
--------------------------------------------------------------------------------
/static/project-images/edutainuproductions.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeyfe6/menefex/HEAD/static/project-images/edutainuproductions.png
--------------------------------------------------------------------------------
/src/styles/_globals.scss:
--------------------------------------------------------------------------------
1 | @use "./customs" as *;
2 |
3 | [hidden] {
4 | display: none;
5 | }
6 |
7 | small {
8 | font-size: 75%;
9 | }
10 |
--------------------------------------------------------------------------------
/src/styles/modules/layout/backdrop.module.scss:
--------------------------------------------------------------------------------
1 | .backdrop {
2 | position: fixed;
3 | top: 0;
4 | left: 0;
5 | width: 100%;
6 | height: 100%;
7 | z-index: 1;
8 | }
9 |
--------------------------------------------------------------------------------
/src/styles/_swiper.scss:
--------------------------------------------------------------------------------
1 | @use "./customs/" as *;
2 |
3 | .swiper {
4 | &-pagination-bullet {
5 | &:hover {
6 | background-color: $primary-color;
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/gatsby-browser.js:
--------------------------------------------------------------------------------
1 | export { wrapRoot as wrapRootElement } from "./src/lib/wrapRoot";
2 |
3 | import "./src/styles/resets.scss";
4 |
5 | export const onServiceWorkerUpdateReady = () => {
6 | window.location.reload();
7 | };
8 |
--------------------------------------------------------------------------------
/src/styles/customs/index.scss:
--------------------------------------------------------------------------------
1 | @forward "colors";
2 | @forward "fonts";
3 | @forward "functions";
4 | @forward "mediaqueries";
5 | @forward "mixins";
6 | @forward "selectors";
7 | @forward "variables";
8 | @forward "utils";
9 | @forward "keyframes";
10 |
--------------------------------------------------------------------------------
/src/styles/modules/forms/callForm.module.scss:
--------------------------------------------------------------------------------
1 | @use "../../customs" as *;
2 |
3 | .call-form {
4 | > hr {
5 | width: rem-calc(48);
6 | border: 3px solid $lightgrey-color;
7 | margin: rem-calc(16 0 32);
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/.stylelintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "stylelint-config-standard-scss",
4 | "stylelint-config-prettier-scss"
5 | ],
6 | "rules": {
7 | "media-feature-range-notation": "context",
8 | "scss/dollar-variable-empty-line-before": null
9 | }
10 | }
--------------------------------------------------------------------------------
/src/components/navbar/menuOverlay.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import * as backDropStyles from "../../styles/modules/layout/backdrop.module.scss";
4 |
5 | const MenuOverlay = ({ click }) => (
6 |
12 | );
13 |
14 | export default MenuOverlay;
15 |
--------------------------------------------------------------------------------
/src/context/root-element.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import { I18nextProvider } from "react-i18next";
4 |
5 | import { HydrationProvider } from "./hydration-context";
6 | import i18n from "../i18n/config";
7 |
8 | const RootElement = ({ children }) => (
9 |
10 | {children}
11 |
12 | );
13 |
14 | export default RootElement;
15 |
--------------------------------------------------------------------------------
/src/styles/modules/ui/notes.module.scss:
--------------------------------------------------------------------------------
1 | @use "../../customs" as *;
2 |
3 | .notes {
4 | height: fit-content;
5 |
6 | @include lemon-bg;
7 | @include box-padding;
8 |
9 | h3 {
10 | margin-bottom: rem-calc(16);
11 | }
12 |
13 | p:not(:last-child) {
14 | margin-bottom: rem-calc(16);
15 |
16 | @include fluid-typing(17, 19);
17 | }
18 |
19 | a {
20 | @include text-link;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/styles/customs/_variables.scss:
--------------------------------------------------------------------------------
1 | $high-radius: 1rem !default;
2 | $medium-radius: 0.5rem !default;
3 | $low-radius: 0.375rem !default;
4 | $less-radius: 0.25rem !default;
5 | $rounded: 50% !default;
6 |
7 | $extreme-spacing: 0.1rem !default;
8 | $high-spacing: 0.05rem !default;
9 | $medium-spacing: 0.0375rem !default;
10 | $low-spacing: 0.025rem !default;
11 | $less-spacing: 0.0125rem !default;
12 |
13 | $layout: 1300 !default;
14 |
15 | $banner: 50 !default;
16 |
--------------------------------------------------------------------------------
/src/components/navbar/hamburger.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import * as hamburgerStyles from "../../styles/modules/layout/hamburger.module.scss";
4 |
5 | const Hamburger = ({ click }) => (
6 |
16 | );
17 |
18 | export default Hamburger;
19 |
--------------------------------------------------------------------------------
/src/styles/customs/_functions.scss:
--------------------------------------------------------------------------------
1 | @use "sass:math";
2 | @use "sass:list";
3 |
4 | @function rem-calc($values, $base-font-size: 16) {
5 | $result: ();
6 |
7 | @each $value in $values {
8 | $converted: null;
9 | @if $value == 0 {
10 | $converted: 0;
11 | } @else {
12 | $converted: math.div($value, $base-font-size) * 1rem;
13 | }
14 | $result: list.append($result, $converted);
15 | }
16 |
17 | @return $result;
18 | }
19 |
--------------------------------------------------------------------------------
/src/context/hydration-context.jsx:
--------------------------------------------------------------------------------
1 | import React, { createContext, useEffect, useState } from "react";
2 |
3 | export const HydrationContext = createContext();
4 |
5 | export const HydrationProvider = ({ children }) => {
6 | const [isHydrated, setIsHydrated] = useState(false);
7 |
8 | useEffect(() => {
9 | setIsHydrated(true);
10 | }, []);
11 |
12 | return (
13 |
14 | {children}
15 |
16 | );
17 | };
18 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "type": "chrome",
9 | "request": "launch",
10 | "name": "Launch Chrome against localhost",
11 | "url": "http://localhost:8080",
12 | "webRoot": "${workspaceFolder}"
13 | }
14 | ]
15 | }
--------------------------------------------------------------------------------
/src/lib/wrapRoot.js:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import { ContentfulLivePreviewProvider } from "@contentful/live-preview/react";
4 |
5 | import RootElement from "../context/root-element";
6 |
7 | export const wrapRoot = ({ element }) => (
8 |
14 | {element}
15 |
16 | );
17 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: "npm" # See documentation for possible values
9 | directory: "/" # Location of package manifests
10 | schedule:
11 | interval: "weekly"
12 |
--------------------------------------------------------------------------------
/src/styles/modules/templates/topic.module.scss:
--------------------------------------------------------------------------------
1 | @use "../../customs" as *;
2 |
3 | .topic {
4 | &-container {
5 | @include container;
6 |
7 | ul {
8 | display: flex;
9 | flex-direction: column;
10 | gap: rem-calc(36);
11 |
12 | li {
13 | a {
14 | @include blog-card;
15 | }
16 | }
17 | }
18 |
19 | > a {
20 | margin-top: rem-calc(64);
21 | margin-left: auto;
22 |
23 | @include primary-button;
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/styles/modules/pages/index.module.scss:
--------------------------------------------------------------------------------
1 | @use "../../customs" as *;
2 |
3 | .vertical-line-long {
4 | margin: rem-calc(24) auto rem-calc(48);
5 | height: rem-calc(80);
6 | }
7 |
8 | .vertical-line-short {
9 | margin: rem-calc(48) auto;
10 | height: rem-calc(48);
11 | }
12 |
13 | .vertical-line-long,
14 | .vertical-line-short {
15 | border-radius: $low-radius;
16 | display: table;
17 | opacity: 0.5;
18 | width: rem-calc(4);
19 | background: linear-gradient(
20 | 145deg,
21 | $grey-color,
22 | $secondary-color,
23 | transparent
24 | );
25 | }
26 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Supported Versions
4 |
5 | Use this section to tell people about which versions of your project are
6 | currently being supported with security updates.
7 |
8 | | Version | Supported |
9 | | ------- | ------------------ |
10 | | 1.8.7 | :white_check_mark: |
11 | | < 1.8.7 | :x: |
12 |
13 | ## Reporting a Vulnerability
14 |
15 | Use this section to tell people how to report a vulnerability.
16 |
17 | Tell them where to go, how often they can expect to get an update on a
18 | reported vulnerability, what to expect if the vulnerability is accepted or
19 | declined, etc.
20 |
--------------------------------------------------------------------------------
/src/components/google/adsmulti.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 |
3 | // TODO: klaar voor TS'en..
4 |
5 | const GoogleAdsMulti = ({ slot }) => {
6 | useEffect(() => {
7 | (window.adsbygoogle = window.adsbygoogle || []).push({});
8 | return () => {};
9 | }, []);
10 |
11 | return (
12 |
19 | );
20 | };
21 |
22 | export default GoogleAdsMulti;
23 |
--------------------------------------------------------------------------------
/src/styles/modules/ui/spotify.module.scss:
--------------------------------------------------------------------------------
1 | @use "../../customs" as *;
2 |
3 | .spotify {
4 | text-align: center;
5 | width: 100%;
6 | height: fit-content;
7 | padding: rem-calc(20);
8 | max-width: rem-calc(475);
9 | margin-left: auto;
10 |
11 | @include lemon-bg;
12 |
13 | @include phablet {
14 | padding: rem-calc(24);
15 | min-width: rem-calc(475);
16 | }
17 |
18 | p {
19 | margin-bottom: rem-calc(32);
20 |
21 | @extend %as-small;
22 | }
23 |
24 | iframe {
25 | height: rem-calc(650);
26 | border: none;
27 | width: -webkit-fill-available;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/components/google/adsdisp.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 |
3 | // TODO: klaar voor TS'en..
4 |
5 | const GoogleAdsDisplay = ({ slot }) => {
6 | useEffect(() => {
7 | (window.adsbygoogle = window.adsbygoogle || []).push({});
8 | return () => {};
9 | }, []);
10 |
11 | return (
12 |
20 | );
21 | };
22 |
23 | export default GoogleAdsDisplay;
24 |
--------------------------------------------------------------------------------
/src/components/ui/notes.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import useTranslation from "../../hooks/use-translation";
4 |
5 | import * as notesStyles from "../../styles/modules/ui/notes.module.scss";
6 |
7 | // TODO: klaar voor TS'en..
8 |
9 | const Notes = () => {
10 | const { t, isHydrated } = useTranslation();
11 |
12 | if (!isHydrated) return null;
13 |
14 | return (
15 |
16 | {t("prices.notes.title")}
17 |
20 |
21 | );
22 | };
23 |
24 | export default Notes;
25 |
--------------------------------------------------------------------------------
/src/styles/customs/_utils.scss:
--------------------------------------------------------------------------------
1 | @use "variables" as *;
2 |
3 | @mixin fluid-typing(
4 | $font-min,
5 | $font-max,
6 | $screen-min: 320,
7 | $screen-max: $layout
8 | ) {
9 | font-size: #{$font-min}px;
10 |
11 | @media only screen and (min-width: #{$screen-min}px) {
12 | /* stylelint-disable-next-line no-invalid-position-declaration */
13 | font-size: calc(
14 | #{$font-min}px + #{($font-max - $font-min)} *
15 | (100vw - #{$screen-min}px) / (#{$screen-max} - #{$screen-min})
16 | );
17 | }
18 |
19 | @media only screen and (min-width: #{$screen-max}px) {
20 | /* stylelint-disable-next-line no-invalid-position-declaration */
21 | font-size: #{$font-max}px;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/styles/modules/pages/contact.module.scss:
--------------------------------------------------------------------------------
1 | @use "../../customs" as *;
2 |
3 | .form-info {
4 | padding-top: rem-calc(48);
5 |
6 | @include phablet {
7 | padding-top: rem-calc(60);
8 | }
9 |
10 | @include tablet {
11 | padding-top: rem-calc(80);
12 | }
13 |
14 | &-container {
15 | display: flex;
16 | gap: rem-calc(48);
17 | flex-direction: column;
18 |
19 | @include container;
20 |
21 | @include tablet {
22 | flex-direction: row;
23 | }
24 |
25 | @include desktop {
26 | gap: rem-calc(64);
27 | }
28 |
29 | > form {
30 | flex: 50%;
31 | }
32 |
33 | > section {
34 | flex: 50%;
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/hooks/use-site-metadata.jsx:
--------------------------------------------------------------------------------
1 | import { graphql, useStaticQuery } from "gatsby";
2 |
3 | const useSiteMetadata = () => {
4 | const data = useStaticQuery(graphql`
5 | query {
6 | site {
7 | siteMetadata {
8 | siteUrl
9 | title
10 | company
11 |
12 | telephone
13 | email
14 |
15 | author
16 | authorImage
17 | authorEmail
18 |
19 | handle
20 |
21 | image
22 | favicon
23 |
24 | screens
25 | }
26 | }
27 | }
28 | `);
29 | return data.site.siteMetadata;
30 | };
31 |
32 | export default useSiteMetadata;
33 |
--------------------------------------------------------------------------------
/src/styles/modules/layout/hamburger.module.scss:
--------------------------------------------------------------------------------
1 | @use "../../customs" as *;
2 |
3 | .hamburger {
4 | display: flex;
5 | flex-direction: column;
6 | justify-content: space-around;
7 | height: rem-calc(32.5);
8 | width: rem-calc(35);
9 | background-color: transparent;
10 | border: none;
11 | padding: 0;
12 | box-sizing: border-box;
13 | z-index: 6;
14 | position: inherit;
15 |
16 | @include mobile {
17 | height: rem-calc(35);
18 | width: rem-calc(37.5);
19 | }
20 |
21 | > div {
22 | width: 100%;
23 | height: rem-calc(6);
24 | background-color: $primary-color;
25 | border-radius: $low-radius;
26 |
27 | @include smooth-trans;
28 | }
29 |
30 | &:hover {
31 | > div {
32 | background-color: $grey-color;
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/hooks/use-translation.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useContext } from "react";
2 |
3 | import { useTranslation } from "react-i18next";
4 |
5 | import { HydrationContext } from "../context/hydration-context";
6 |
7 | const useTranslationSetup = () => {
8 | const { t, i18n } = useTranslation();
9 | const { isHydrated } = useContext(HydrationContext);
10 |
11 | useEffect(() => {
12 | if (isHydrated) {
13 | const storedLanguage = window.localStorage.getItem("i18nextLng");
14 | if (storedLanguage && i18n.language !== storedLanguage) {
15 | i18n.changeLanguage(storedLanguage);
16 | }
17 | }
18 | }, [isHydrated]);
19 |
20 | useEffect(() => {
21 | document.documentElement.lang = i18n.language;
22 | }, [i18n.language]);
23 |
24 | return { t, isHydrated, i18n };
25 | };
26 |
27 | export default useTranslationSetup;
--------------------------------------------------------------------------------
/src/i18n/config.js:
--------------------------------------------------------------------------------
1 | import i18n from "i18next";
2 | import { initReactI18next } from "react-i18next";
3 |
4 | import nl from "./nl";
5 | import en from "./en";
6 |
7 | // console.log("nl", nl);
8 | // console.log("en", en);
9 |
10 | const resources = {
11 | en: { translation: en || {} },
12 | nl: { translation: nl || {} },
13 | };
14 |
15 | i18n.use(initReactI18next).init({
16 | resources,
17 | fallbackLng: "nl",
18 | debug: process.env.NODE_ENV === "development",
19 | detection: {
20 | order: ["localStorage", "cookie", "navigator"],
21 | caches: ["localStorage", "cookie"],
22 | },
23 | interpolation: {
24 | escapeValue: false,
25 | },
26 | react: {
27 | useSuspense: false,
28 | },
29 | missingKeyHandler: function (lng, ns, key, fallbackValue) {
30 | console.warn(`Missing translation: ${key} (${lng})`);
31 | },
32 | });
33 |
34 | export default i18n;
35 |
--------------------------------------------------------------------------------
/src/components/ui/spotify.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import useTranslation from "../../hooks/use-translation";
4 |
5 | import * as spotifyStyles from "../../styles/modules/ui/spotify.module.scss";
6 |
7 | // TODO: klaar voor TS'en..
8 |
9 | const Spotify = () => {
10 | const { t, isHydrated } = useTranslation();
11 |
12 | if (!isHydrated) return null;
13 |
14 | return (
15 |
25 | );
26 | };
27 |
28 | export default Spotify;
29 |
--------------------------------------------------------------------------------
/src/styles/modules/pages/prices.module.scss:
--------------------------------------------------------------------------------
1 | @use "sass:color";
2 |
3 | @use "../../customs" as *;
4 |
5 | .call-back {
6 | color: $tel-color;
7 | font-weight: 500;
8 |
9 | &:hover {
10 | color: color.adjust($tel-color, $lightness: -5%);
11 | }
12 | }
13 |
14 | .contact-us {
15 | @include text-link;
16 | }
17 |
18 | .notes-call {
19 | display: flex;
20 | gap: rem-calc(24);
21 | flex-direction: column;
22 |
23 | @include container($mb: rem-calc(80));
24 |
25 | @include phablet {
26 | flex-direction: row;
27 | margin-bottom: rem-calc(120);
28 | }
29 |
30 | @include tablet {
31 | margin-bottom: rem-calc(160);
32 | }
33 |
34 | > section {
35 | flex: 50%;
36 |
37 | @include phablet {
38 | flex: 55%;
39 | }
40 | }
41 |
42 | > form {
43 | flex: 50%;
44 |
45 | @include phablet {
46 | flex: 55%;
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/gatsby-ssr.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import { wrapRoot } from "./src/lib/wrapRoot";
4 |
5 | export const wrapRootElement = wrapRoot;
6 |
7 | const HtmlAttributes = {
8 | lang: "nl",
9 | prefix: "og: https://ogp.me/ns#",
10 | };
11 |
12 | const HeadComponents = [
13 | ,
20 | ];
21 |
22 | export const onRenderBody = ({
23 | setHeadComponents,
24 | setHtmlAttributes,
25 | setBodyAttributes,
26 | }) => {
27 | setHtmlAttributes(HtmlAttributes);
28 | setHeadComponents(HeadComponents);
29 | setBodyAttributes({});
30 | };
31 |
32 | export const onPreRenderHTML = ({
33 | getHeadComponents,
34 | replaceHeadComponents,
35 | }) => {
36 | const headComponents = getHeadComponents();
37 | replaceHeadComponents(headComponents);
38 | };
39 |
--------------------------------------------------------------------------------
/src/components/layout/banner.jsx:
--------------------------------------------------------------------------------
1 | import React, { forwardRef } from "react";
2 |
3 | import useTranslation from "../../hooks/use-translation";
4 |
5 | // TODO: klaar voor TS'en..
6 |
7 | const Banner = forwardRef((props, ref) => {
8 | const { t } = useTranslation();
9 |
10 | return (
11 |
28 | );
29 | });
30 |
31 | export default Banner;
32 |
--------------------------------------------------------------------------------
/src/styles/modules/ui/maps.module.scss:
--------------------------------------------------------------------------------
1 | @use "../../customs" as *;
2 |
3 | .maps {
4 | $maps: &;
5 |
6 | &-container {
7 | @include container;
8 |
9 | pointer-events: none !important;
10 |
11 | #{$maps}-content {
12 | filter: url('data:image/svg+xml;utf8,#g');
13 | filter: grayscale(75%);
14 | filter: progid:DXImageTransform.Microsoft.BasicImage(grayScale=1);
15 | outline: 2px solid $lightgrey-color;
16 | border-radius: $low-radius;
17 | overflow: hidden;
18 | width: 100%;
19 | height: rem-calc(150);
20 |
21 | @include mobile {
22 | height: rem-calc(175);
23 | }
24 |
25 | @include tablet {
26 | height: rem-calc(200);
27 | }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/styles/modules/pages/blog.module.scss:
--------------------------------------------------------------------------------
1 | @use "../../customs" as *;
2 |
3 | .blog {
4 | &-container {
5 | @include container;
6 |
7 | > ul {
8 | display: flex;
9 | flex-direction: column;
10 |
11 | > li {
12 | &:not(:last-of-type) {
13 | border-bottom: 1px solid $smoke-color;
14 |
15 | > a {
16 | padding-bottom: rem-calc(24);
17 | }
18 | }
19 |
20 | &:not(:first-of-type) {
21 | > a {
22 | padding-top: rem-calc(24);
23 | }
24 | }
25 |
26 | > a {
27 | @include blog-card;
28 | }
29 | }
30 | }
31 |
32 | /* stylelint-disable-next-line no-descending-specificity */
33 | > a {
34 | margin-top: rem-calc(64);
35 | margin-left: auto;
36 |
37 | @include primary-button;
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/styles/customs/_colors.scss:
--------------------------------------------------------------------------------
1 | $primary-color: #5b5a5a;
2 | $secondary-color: #fc0;
3 |
4 | $lemon-color: #f8e491;
5 | $ivory-color: #fdfae9;
6 |
7 | $dark-color: #323232;
8 | $black-color: #202020;
9 |
10 | $grey-color: #a9a9a9;
11 | $lightgrey-color: #c0c0c0;
12 |
13 | $white-color: #fff;
14 | $smoke-color: #f4f4f4;
15 | $tissue-color: #f2f2f2;
16 | $snow-color: #dadada;
17 |
18 | $link-color: #008cd2;
19 |
20 | $mail-color: #74aece;
21 | $tel-color: #2b9d53;
22 | $whapp-color: #23cd62;
23 |
24 | $error-color: #d9534f;
25 |
26 | $fb-color: #3b5998;
27 | $ig-color: #e1306c;
28 | $tw-color: #1d9bf0;
29 | $li-color: #0077b5;
30 | $gh-color: #24292e;
31 |
32 | // #cdcccc
33 | // #cac9c9
34 | // #c0bfbf
35 | // #afaeae
36 | // #979696
37 | // #787777
38 | // #535252
39 | // #4e4d4d
40 | // #cfa0a0
41 |
42 | // !! TOPICS COlORS
43 | // #DAA972 Doe-het-zelf
44 | // #A72669 Progressie
45 | // #D3100F Nieuws
46 | // #F6C805 Informatie
47 | // #4648C7 SEO
48 | // #AF6B31 Interne Projecten
49 | // #53A5D8 Tips & Weetjes
50 | // #AAD378 Op reis
51 | // #F7F048 De beste van
52 | // #C2D6D5 Vergelijken
53 |
--------------------------------------------------------------------------------
/src/pages/success.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import { Link } from "gatsby";
4 |
5 | import useTranslation from "../hooks/use-translation";
6 |
7 | import SEO from "../components/seo";
8 | import Layout from "../components/layout";
9 |
10 | // TODO: klaar voor TS'en..
11 |
12 | const ThankYouPage = () => {
13 | const { t, isHydrated } = useTranslation();
14 |
15 | if (!isHydrated) return null;
16 |
17 | return (
18 |
19 |
20 | {t("success.title")}
21 | {t("success.text")}
22 |
23 |
24 | {t("success.home")}
25 |
26 |
27 |
28 | );
29 | };
30 |
31 | export default ThankYouPage;
32 |
33 | export const Head = () => (
34 |
40 | );
41 |
--------------------------------------------------------------------------------
/static/ads.txt:
--------------------------------------------------------------------------------
1 | google.com, pub-3158048130288702, DIRECT, f08c47fec0942fa0
2 |
3 | disqus.com, 4454033, DIRECT
4 | zetaglobal.net, 100, DIRECT
5 | amxrtb.com, 105199574, DIRECT
6 | aps.amazon.com, 31d3a4c9-a8a6-4ae0-a5ae-d3b0de43284c, DIRECT
7 | aniview.com, 616704c962b31624e671e171, RESELLER, 78b21b97965ec3f8
8 | criteo.com, B-060574, RESELLER, 9fac4a4a87c2a44f
9 | themediagrid.com, T49HZW, RESELLER, 35d5010d7789b49d
10 | google.com, pub-6650322601660058, RESELLER, f08c47fec0942fa0
11 | rubiconproject.com, 13380, RESELLER, 0bfd66d529a55807
12 | mediafuse.com, 604, RESELLER
13 | minutemedia.com, 01gcrv9grwe9, RESELLER
14 | onetag.com, 5cd7fb62fac7ec9, DIRECT
15 | openx.com, 537133236, RESELLER, 6a698e2ec38604c6
16 | pubmatic.com, 158685, RESELLER, 5d62403b186f2ace
17 | sharethrough.com, UvcAx8IL, DIRECT, d53b998a7bd4ecd2
18 | sonobi.com, 296bf9795d, DIRECT, d1a215d9eb5aee9e
19 | lijit.com, 279534, DIRECT, fafdf38b16bf6b2b
20 | lijit.com, 279534-eb, DIRECT, fafdf38b16bf6b2b
21 | taboola.com, 1003147, DIRECT, c228e6794e811952
22 | themediagrid.com, EGZCXK, DIRECT, 35d5010d7789b49d
23 | themediagrid.com, 7MTKQZ, RESELLER, 35d5010d7789b49d
24 | appnexus.com, 2797, RESELLER
--------------------------------------------------------------------------------
/src/styles/modules/pages/about.module.scss:
--------------------------------------------------------------------------------
1 | @use "../../customs" as *;
2 |
3 | .about {
4 | &-content {
5 | h3 {
6 | margin-bottom: rem-calc(8);
7 |
8 | @include poppins-title;
9 | @include underline;
10 | @extend %as-h5;
11 |
12 | &:not(:first-of-type) {
13 | margin-top: rem-calc(32);
14 | }
15 | }
16 |
17 | a {
18 | @include text-link;
19 | }
20 |
21 | div p:not(:last-child) {
22 | margin-bottom: rem-calc(16);
23 | }
24 | }
25 |
26 | &-spotify {
27 | padding: rem-calc(48 0 80);
28 |
29 | @include phablet {
30 | padding: rem-calc(60 0 120);
31 | }
32 |
33 | @include tablet {
34 | padding: rem-calc(80 0 160);
35 | }
36 |
37 | &-container {
38 | display: flex;
39 | gap: rem-calc(80);
40 | flex-direction: column;
41 |
42 | @include container;
43 |
44 | @include tablet {
45 | flex-direction: row;
46 | }
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/styles/customs/_keyframes.scss:
--------------------------------------------------------------------------------
1 | @keyframes scroll-left {
2 | to {
3 | transform: translateX(-50%);
4 | }
5 | }
6 |
7 | @keyframes hero-roll {
8 | 0% {
9 | transform: rotate(0deg);
10 | }
11 |
12 | 25% {
13 | transform: rotate(180deg);
14 | }
15 |
16 | 50% {
17 | transform: rotate(360deg);
18 | }
19 |
20 | 75% {
21 | transform: rotate(0deg);
22 | }
23 |
24 | 100% {
25 | transform: rotate(360deg);
26 | }
27 | }
28 |
29 | @keyframes footer-roll {
30 | 0% {
31 | transform: rotate(360deg);
32 | }
33 |
34 | 25% {
35 | transform: rotate(180deg);
36 | }
37 |
38 | 50% {
39 | transform: rotate(360deg);
40 | }
41 |
42 | 75% {
43 | transform: rotate(0deg);
44 | }
45 |
46 | 100% {
47 | transform: rotate(360deg);
48 | }
49 | }
50 |
51 | @keyframes fade-down {
52 | 0% {
53 | transform: translate(0, -20px) rotate(45deg);
54 | opacity: 0;
55 | }
56 |
57 | 50% {
58 | opacity: 1;
59 | }
60 |
61 | 100% {
62 | transform: translate(0, 20px) rotate(45deg);
63 | opacity: 0;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/pages/404.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import { Link } from "gatsby";
4 |
5 | import useTranslation from "../hooks/use-translation";
6 |
7 | import Layout from "../components/layout";
8 | import SEO from "../components/seo";
9 |
10 | // TODO: klaar voor TS'en..
11 |
12 | const NotFound = () => {
13 | const { t, isHydrated } = useTranslation();
14 |
15 | if (!isHydrated) return null;
16 |
17 | return (
18 |
19 |
20 |
21 | {t("notfound.sigh")}
22 | .. {t("notfound.page")}
23 | . {t("notfound.tooBad")}
24 | ..
25 |
26 |
27 | {t("notfound.home")}
28 |
29 |
30 |
31 |
32 | );
33 | };
34 |
35 | export default NotFound;
36 |
37 | export const Head = () => (
38 |
44 | );
45 |
--------------------------------------------------------------------------------
/src/styles/customs/_mediaqueries.scss:
--------------------------------------------------------------------------------
1 | @mixin mini {
2 | // 368px
3 | @media (width >= 23em) {
4 | @content;
5 | }
6 | }
7 |
8 | @mixin compact {
9 | // 480px
10 | @media (width >= 30em) {
11 | @content;
12 | }
13 | }
14 |
15 | @mixin mobile {
16 | // 640px
17 | @media (width >= 40em) {
18 | @content;
19 | }
20 | }
21 |
22 | @mixin phablet {
23 | // 768px
24 | @media (width >= 52em) {
25 | @content;
26 | }
27 | }
28 |
29 | @mixin tablet {
30 | // 1024px
31 | @media (width >= 64em) {
32 | @content;
33 | }
34 | }
35 |
36 | @mixin laptop {
37 | // 1280px
38 | @media (width >= 76em) {
39 | @content;
40 | }
41 | }
42 |
43 | @mixin desktop {
44 | // 1440px
45 | @media (width >= 90em) {
46 | @content;
47 | }
48 | }
49 |
50 | @mixin wide {
51 | // 1600px
52 | @media (width >= 100em) {
53 | @content;
54 | }
55 | }
56 |
57 | @mixin massive {
58 | // 1920px
59 | @media (width >= 120em) {
60 | @content;
61 | }
62 | }
63 |
64 | // Orientation-based media queries (for mobile-first)
65 |
66 | @mixin landscape {
67 | @media (width >= 23em) and (width <= 64em) and (orientation: landscape) {
68 | @content;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/.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 |
71 | # VScode
72 | .vscode/
73 | # Local Netlify folder
74 | .netlify
75 |
76 | package-lock.json
--------------------------------------------------------------------------------
/src/styles/modules/ui/biography.module.scss:
--------------------------------------------------------------------------------
1 | @use "../../customs" as *;
2 |
3 | .biography {
4 | $biography: &;
5 |
6 | h3 {
7 | @include section-title;
8 | }
9 |
10 | &-container {
11 | @include container;
12 |
13 | #{$biography}-wrapper {
14 | padding: rem-calc(20 24);
15 |
16 | @include border($grey-color);
17 |
18 | @include phablet {
19 | padding: rem-calc(36 48);
20 | }
21 |
22 | > p {
23 | @include fluid-typing(17, 20);
24 |
25 | a {
26 | @include text-link;
27 | }
28 | }
29 |
30 | > div {
31 | display: flex;
32 | justify-content: space-between;
33 | align-items: center;
34 |
35 | a {
36 | &:first-of-type {
37 | color: $primary-color;
38 | font-size: rem-calc(32);
39 |
40 | &:hover {
41 | color: $link-color;
42 | }
43 | }
44 |
45 | &.meerover {
46 | @include primary-button;
47 | }
48 | }
49 | }
50 |
51 | #{$biography}-text {
52 | margin-bottom: rem-calc(40);
53 | }
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/components/ui/about.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import useTranslation from "../../hooks/use-translation";
4 |
5 | import * as aboutStyles from "../../styles/modules/pages/about.module.scss";
6 |
7 | // TODO: klaar voor TS'en..
8 |
9 | const About = () => {
10 | const { t, isHydrated } = useTranslation();
11 |
12 | if (!isHydrated) return null;
13 |
14 | return (
15 |
16 | {t("about.whoAreWe.title")}
17 |
22 |
23 | {t("about.howWeCameAbout.title")}
24 |
29 |
30 | {t("about.whatWeStandFor.title")}
31 |
36 |
37 | {t("about.unique.title")}
38 |
39 |
40 | {t("about.goals.title")}
41 |
42 |
43 | );
44 | };
45 |
46 | export default About;
47 |
--------------------------------------------------------------------------------
/src/components/helpers/responsiveTag.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, FC } from "react";
2 |
3 | // interface Breakpoint {
4 | // size: number;
5 | // label: string;
6 | // }
7 |
8 | // const ResponsiveTag: FC = () => {
9 | const ResponsiveTag = () => {
10 | const breakpoints = [
11 | // const breakpoints: Breakpoint[] = [
12 | { size: 23, label: "Mini View" },
13 | { size: 30, label: "Compact View" },
14 | { size: 40, label: "Mobile View" },
15 | { size: 52, label: "Phablet View" },
16 | { size: 64, label: "Tablet View" },
17 | { size: 76, label: "Laptop View" },
18 | { size: 90, label: "Desktop View" },
19 | { size: 100, label: "Wide View" },
20 | { size: Infinity, label: "Massive View" },
21 | ];
22 |
23 | const [screenSize, setScreenSize] = useState("");
24 |
25 | const handleResize = () => {
26 | const width = window.innerWidth / 16;
27 | const breakpoint = breakpoints.find(
28 | (breakpoint) => width <= breakpoint.size
29 | );
30 | if (breakpoint) {
31 | setScreenSize(breakpoint.label);
32 | }
33 | };
34 |
35 | useEffect(() => {
36 | handleResize();
37 | window.addEventListener("resize", handleResize);
38 |
39 | return () => {
40 | window.removeEventListener("resize", handleResize);
41 | };
42 | }, []);
43 |
44 | return (
45 |
48 | );
49 | };
50 |
51 | export default ResponsiveTag;
52 |
--------------------------------------------------------------------------------
/src/utils/fetchLastCommitDate.js:
--------------------------------------------------------------------------------
1 | const axios = require("axios");
2 |
3 | const excludedPages = [
4 | "src/pages/topics/",
5 | "src/pages/blog/",
6 | "src/pages/offline-plugin-app-shell-fallback.jsx",
7 | "src/pages/404.html.jsx",
8 | ];
9 |
10 | const isExcluded = (filePath) =>
11 | excludedPages.some((page) => filePath.startsWith(page));
12 |
13 | const fetchLastCommitDate = async (filePath) => {
14 | const githubToken = process.env.GATSBY_GITHUB_TOKEN;
15 | const repoOwner = "mikeyfe6";
16 | const repoName = "menefex";
17 |
18 | if (isExcluded(filePath)) {
19 | console.warn(`Skipping commit check for excluded path: ${filePath}`);
20 | return null;
21 | }
22 |
23 | try {
24 | const response = await axios.get(
25 | `https://api.github.com/repos/${repoOwner}/${repoName}/commits`,
26 | {
27 | params: {
28 | path: filePath,
29 | per_page: 1,
30 | },
31 | headers: {
32 | Authorization: `token ${githubToken}`,
33 | },
34 | }
35 | );
36 |
37 | if (response.data.length === 0) {
38 | console.warn(`No commits found for path: ${filePath}`);
39 | return null;
40 | }
41 |
42 | const lastCommitDate = response.data[0].commit.committer.date;
43 | return lastCommitDate;
44 | } catch (error) {
45 | console.error(`Error fetching last commit date: ${error.message}`);
46 | return null;
47 | }
48 | };
49 |
50 | module.exports = fetchLastCommitDate;
51 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | # Menefex Webmediabureau
15 |
16 |
17 |
18 | Menefex is een jong en modern webmediabureau, opgericht door [Michael Fransman](https://www.linkedin.com/in/michaelfransman/).
19 |
20 | Die zich inzet en gespecialiseerd is in het uitdenken en ontwikkelen van websites en webapplicaties. Wij zorgen dat de ambities van de klant en het eindproduct tot één komen.
21 |
22 | - _Bekijk ook onze website / Also check our website: [Menefex Webmediabureau](https://menefex.nl)_ ✨
23 |
24 |
25 |
26 | ---
27 |
28 |
29 |
30 | **Deze site is gebouwd met GatsbyJS, ReactJS, Contentful, GraphQl & Netlify.**
31 |
32 | 🙋🏾♂️ Author: Michael Fransman
33 |
34 |
35 |
36 | ## Licence ✒️
37 |
38 | GNU AGPLv3, all rights reserved, Menefex WMB 2019 - 2025 ©
39 |
40 |
41 |
42 | ---
43 |
44 |
45 |
46 | [](https://app.netlify.com/sites/menefex/deploys)
47 | 👨🏾💻 ...this project
[](https://wakatime.com/badge/github/mikeyfe6/Menefex)
48 | 👨🏾💻 ...all projects
[](https://wakatime.com/@172a37f5-1ff4-4cf8-9d08-5ccbe681726b)
49 |
50 |
51 |
--------------------------------------------------------------------------------
/src/styles/modules/pages/topics.module.scss:
--------------------------------------------------------------------------------
1 | @use "../../customs" as *;
2 |
3 | .topics {
4 | &-container {
5 | @include container;
6 |
7 | > ul {
8 | display: flex;
9 | flex-direction: column;
10 | gap: rem-calc(16);
11 |
12 | li {
13 | a {
14 | background: linear-gradient(
15 | to right,
16 | $tissue-color,
17 | $smoke-color
18 | );
19 | padding: rem-calc(16);
20 | display: flex;
21 | flex-direction: column;
22 | gap: rem-calc(8);
23 | border-width: 2px;
24 | border-style: solid;
25 | border-radius: $medium-radius;
26 |
27 | > h3 {
28 | @extend %as-h4;
29 |
30 | span {
31 | font-weight: bold;
32 | }
33 | }
34 |
35 | > p {
36 | @extend %as-medium;
37 | }
38 |
39 | &:hover {
40 | border-color: $link-color;
41 | transform: scale(1.0125);
42 |
43 | h3 {
44 | color: $link-color;
45 | }
46 | }
47 | }
48 | }
49 | }
50 |
51 | /* stylelint-disable-next-line no-descending-specificity */
52 | > a {
53 | margin-top: rem-calc(64);
54 | margin-left: auto;
55 |
56 | @include primary-button;
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/pages/privacybeleid.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import useTranslation from "../hooks/use-translation";
4 | import useSiteMetadata from "../hooks/use-site-metadata";
5 |
6 | import Layout from "../components/layout";
7 | import SEO from "../components/seo";
8 |
9 | const PrivacyPolicy = () => {
10 | const { t, isHydrated } = useTranslation();
11 |
12 | if (!isHydrated) return null;
13 |
14 | return (
15 |
16 |
22 |
23 | );
24 | };
25 |
26 | export default PrivacyPolicy;
27 |
28 | export const Head = () => {
29 | const { siteUrl } = useSiteMetadata();
30 |
31 | const pageTitle = "Privacybeleid";
32 | const pageSlug = "/privacybeleid/";
33 |
34 | const breadcrumbSchema = {
35 | "@context": "https://schema.org",
36 | "@type": "BreadcrumbList",
37 | name: pageTitle,
38 | itemListElement: [
39 | {
40 | "@type": "ListItem",
41 | position: 1,
42 | name: pageTitle,
43 | item: siteUrl + pageSlug,
44 | },
45 | ],
46 | };
47 |
48 | return (
49 |
56 | );
57 | };
58 |
--------------------------------------------------------------------------------
/src/images/logo/mnfx-icon.svg:
--------------------------------------------------------------------------------
1 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/components/ui/biography.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import { Link } from "gatsby";
4 |
5 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
6 |
7 | import useTranslation from "../../hooks/use-translation";
8 |
9 | import * as biographyStyles from "../../styles/modules/ui/biography.module.scss";
10 |
11 | // TODO: klaar voor TS'en..
12 |
13 | const Biography = () => {
14 | const { t, isHydrated } = useTranslation();
15 |
16 | if (!isHydrated) return null;
17 |
18 | return (
19 |
20 |
21 |
22 |
{t("biography.title")}
23 |
29 |
30 |
31 |
37 |
38 |
39 |
40 | {t("biography.more")}
41 |
42 |
43 |
44 |
45 |
46 | );
47 | };
48 |
49 | export default Biography;
50 |
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | name: "CodeQL"
2 |
3 | on:
4 | push:
5 | branches: [master]
6 | pull_request:
7 | branches: [master]
8 | schedule:
9 | - cron: "17 5 * * 1"
10 |
11 | jobs:
12 | analyze:
13 | name: Analyze
14 | runs-on: ubuntu-latest
15 | permissions:
16 | actions: read
17 | contents: read
18 | security-events: write
19 |
20 | strategy:
21 | fail-fast: false
22 | matrix:
23 | language: ["javascript"]
24 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
25 | # Learn more about CodeQL language support at https://git.io/codeql-language-support
26 |
27 | steps:
28 | - name: Checkout repository
29 | uses: actions/checkout@v4 # This is fine, no changes needed
30 |
31 | # Initializes the CodeQL tools for scanning.
32 | - name: Initialize CodeQL
33 | uses: github/codeql-action/init@v3 # Updated to v3
34 |
35 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
36 | # If this step fails, then you should remove it and run the build manually (see below)
37 | - name: Autobuild
38 | uses: github/codeql-action/autobuild@v3 # Updated to v3
39 |
40 | # ℹ️ Command-line programs to run using the OS shell.
41 | # 📚 https://git.io/JvXDl
42 |
43 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
44 | # and modify them (or add more) to build your code if your project
45 | # uses a compiled language
46 |
47 | #- run: |
48 | # make bootstrap
49 | # make release
50 |
51 | - name: Perform CodeQL Analysis
52 | uses: github/codeql-action/analyze@v3 # Updated to v3
53 |
--------------------------------------------------------------------------------
/src/pages/algemene-voorwaarden.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import useSiteMetadata from "../hooks/use-site-metadata";
4 | import useTranslation from "../hooks/use-translation";
5 |
6 | import SEO from "../components/seo";
7 | import Layout from "../components/layout";
8 |
9 | const TermsConditions = () => {
10 | const { t, isHydrated } = useTranslation();
11 |
12 | if (!isHydrated) return null;
13 |
14 | return (
15 |
16 |
22 |
23 | );
24 | };
25 |
26 | export default TermsConditions;
27 |
28 | export const Head = () => {
29 | const { siteUrl } = useSiteMetadata();
30 |
31 | const pageTitle = "Algemene Voorwaarden";
32 | const pageSlug = "/algemene-voorwaarden/";
33 |
34 | const breadcrumbSchema = {
35 | "@context": "https://schema.org",
36 | "@type": "BreadcrumbList",
37 | name: pageTitle,
38 | itemListElement: [
39 | {
40 | "@type": "ListItem",
41 | position: 1,
42 | name: pageTitle,
43 | item: siteUrl + pageSlug,
44 | },
45 | ],
46 | };
47 |
48 | return (
49 |
56 | );
57 | };
58 |
--------------------------------------------------------------------------------
/src/styles/customs/_selectors.scss:
--------------------------------------------------------------------------------
1 | @use "colors" as *;
2 | @use "utils" as *;
3 | @use "functions" as *;
4 | @use "mediaqueries" as *;
5 | @use "mixins" as *;
6 |
7 | %image {
8 | --border: 4px; /* border thickness */
9 | --size: 24px; /* size of the corner */
10 | --gap: 14px; /* the gap */
11 | --color: #{rgba($secondary-color, 0.9)}; /* color of the border */
12 | --color-two: #{rgba($primary-color, 0.9)}; /* color of the border */
13 |
14 | padding: calc(var(--border) + var(--gap));
15 | background-image: conic-gradient(
16 | from 90deg at top var(--border) left var(--border),
17 | #0000 25%,
18 | var(--color) 0
19 | ),
20 | conic-gradient(
21 | from -90deg at bottom var(--border) right var(--border),
22 | #0000 25%,
23 | var(--color-two) 0
24 | );
25 | background-position: var(--position, 0%) var(--position, 0%),
26 | calc(100% - var(--position, 0%)) calc(100% - var(--position, 0%));
27 | background-size: var(--size) var(--size);
28 | background-repeat: no-repeat;
29 | transition: background-position 0.3s var(--transition, 0.3s),
30 | background-size 0.3s calc(0.3s - var(--transition, 0.3s));
31 |
32 | &:hover {
33 | background-size: calc(100% - var(--gap)) calc(100% - var(--gap));
34 |
35 | --position: calc(var(--gap) / 2);
36 | --transition: 0s;
37 | }
38 | }
39 |
40 | %as-h1 {
41 | @include fluid-typing(36, 40);
42 | }
43 |
44 | %as-h2 {
45 | @include fluid-typing(32, 36);
46 | }
47 |
48 | %as-h3 {
49 | @include fluid-typing(28, 32);
50 | }
51 |
52 | %as-h4 {
53 | @include fluid-typing(24, 28);
54 | }
55 |
56 | %as-h5 {
57 | @include fluid-typing(20, 24);
58 | }
59 |
60 | %as-h6 {
61 | @include fluid-typing(16, 20);
62 | }
63 |
64 | %as-p {
65 | @include fluid-typing(18, 20);
66 | }
67 |
68 | %as-medium {
69 | @include fluid-typing(16, 18);
70 | }
71 |
72 | %as-small {
73 | @include fluid-typing(14, 16);
74 | }
75 |
76 | %as-tiny {
77 | @include fluid-typing(12, 14);
78 | }
79 |
80 | %recaptcha {
81 | display: block;
82 | margin-top: rem-calc(16);
83 | color: rgba($primary-color, 50%);
84 | background: $white-color;
85 | padding: rem-calc(12 16);
86 | border: 1px solid rgba($primary-color, 25%);
87 |
88 | > a {
89 | @include text-link;
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/styles/_cookie.scss:
--------------------------------------------------------------------------------
1 | @use "./customs" as *;
2 |
3 | .cookie {
4 | $cookie: &;
5 |
6 | position: fixed;
7 | width: 100%;
8 | height: 100%;
9 | inset: 0;
10 | background-color: rgb(0 0 0 / 50%);
11 | z-index: 10;
12 |
13 | &-container {
14 | background: linear-gradient(145deg, $primary-color, $dark-color);
15 | opacity: 0.95;
16 | color: $snow-color;
17 | border-bottom: 7.5px solid $secondary-color;
18 | border-top: 7.5px solid $primary-color;
19 | width: calc(100% - 2.5rem);
20 | max-width: rem-calc(1200);
21 | margin: 0 auto;
22 | padding: rem-calc(24);
23 | position: absolute;
24 | left: 50%;
25 | transform: translate(-50%, -12.5%);
26 | border-radius: $low-radius;
27 |
28 | #{$cookie}-content {
29 | > p {
30 | color: $white-color;
31 | margin: rem-calc(16 0 24);
32 |
33 | @extend %as-medium;
34 |
35 | > a {
36 | color: $secondary-color;
37 |
38 | &:hover {
39 | color: $link-color;
40 | }
41 | }
42 |
43 | > span {
44 | display: block;
45 | margin: rem-calc(16 0);
46 |
47 | @include fluid-typing(15, 17);
48 | }
49 | }
50 | }
51 |
52 | #{$cookie}-btns {
53 | display: flex;
54 | justify-content: flex-end;
55 | gap: rem-calc(16);
56 | align-items: center;
57 |
58 | #{$cookie}-btn {
59 | &-accept,
60 | &-decline {
61 | border-radius: $low-radius;
62 | padding: rem-calc(8 16);
63 | }
64 |
65 | &-accept {
66 | color: $dark-color;
67 | background-color: $secondary-color;
68 |
69 | &:hover {
70 | background-color: $ivory-color;
71 | }
72 | }
73 |
74 | &-decline {
75 | border: 1px solid #dadada86;
76 | color: $secondary-color;
77 |
78 | &:hover {
79 | border-color: $white-color;
80 | }
81 | }
82 | }
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/components/google/maps.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 |
3 | import GoogleMapReact from "google-map-react";
4 |
5 | import useSiteMetadata from "../../hooks/use-site-metadata";
6 |
7 | import mapsLogo from "../../images/logo/mnfx-icon.svg";
8 |
9 | import * as mapsStyles from "../../styles/modules/ui/maps.module.scss";
10 |
11 | // TODO: klaar voor TS'en..
12 |
13 | const useScreenSize = () => {
14 | const [isMobile, setIsMobile] = useState(false);
15 |
16 | useEffect(() => {
17 | const checkScreenSize = () => {
18 | setIsMobile(window.innerWidth < 480);
19 | };
20 |
21 | checkScreenSize();
22 |
23 | window.addEventListener("resize", checkScreenSize);
24 |
25 | return () => window.removeEventListener("resize", checkScreenSize);
26 | }, []);
27 |
28 | return isMobile;
29 | };
30 |
31 | const defaultProps = {
32 | center: {
33 | lat: 52.31049600748774,
34 | lng: 4.973736770446289,
35 | },
36 | };
37 |
38 | const mapOptions = {
39 | disableDefaultUI: true,
40 | };
41 |
42 | const Marker = ({ lat, lng }) => {
43 | const { title } = useSiteMetadata();
44 |
45 | return (
46 |
47 |

48 |
49 | );
50 | };
51 |
52 | const SimpleMap = () => {
53 | const isMobile = useScreenSize();
54 |
55 | const responsiveZoom = isMobile ? 15.25 : 16;
56 |
57 | return (
58 |
59 |
60 |
61 |
71 |
75 |
76 |
77 |
78 |
79 | );
80 | };
81 |
82 | export default SimpleMap;
83 |
--------------------------------------------------------------------------------
/src/styles/modules/ui/usp.module.scss:
--------------------------------------------------------------------------------
1 | @use "../../customs" as *;
2 |
3 | .usp {
4 | $usp: &;
5 |
6 | &-container {
7 | @include container;
8 |
9 | > ul {
10 | display: flex;
11 | flex-wrap: nowrap;
12 | justify-content: flex-start;
13 | gap: rem-calc(32);
14 | overflow-x: auto;
15 | scroll-snap-type: x mandatory;
16 | -webkit-overflow-scrolling: touch;
17 |
18 | &::-webkit-scrollbar {
19 | display: none;
20 | }
21 |
22 | > li {
23 | flex: 0 0 auto;
24 | display: flex;
25 | flex-direction: column;
26 | justify-content: center;
27 | align-items: center;
28 | width: rem-calc(225);
29 | scroll-snap-align: start;
30 |
31 | > svg {
32 | color: $primary-color;
33 | font-size: xx-large;
34 | background-color: $secondary-color;
35 | border-radius: $low-radius;
36 | padding: rem-calc(8);
37 | }
38 |
39 | > p {
40 | display: flex;
41 | flex-direction: column;
42 | justify-content: space-between;
43 | align-items: center;
44 | width: 100%;
45 | height: rem-calc(220);
46 | padding: rem-calc(16);
47 | border-radius: $medium-radius;
48 | margin-top: rem-calc(20);
49 | text-align: center;
50 |
51 | @include fluid-typing(17, 19);
52 |
53 | > svg {
54 | color: $snow-color;
55 |
56 | &:first-child {
57 | align-self: flex-start;
58 | }
59 |
60 | &:last-child {
61 | align-self: flex-end;
62 | }
63 | }
64 | }
65 |
66 | &:nth-child(odd) {
67 | > p {
68 | background-color: $ivory-color;
69 | }
70 | }
71 |
72 | &:nth-child(even) {
73 | p {
74 | background-color: $smoke-color;
75 | }
76 | }
77 | }
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/styles/modules/layout/mobileMenu.module.scss:
--------------------------------------------------------------------------------
1 | @use "../../customs" as *;
2 |
3 | .mobile-menu {
4 | height: calc(100% - #{rem-calc($banner)});
5 | opacity: 0.95;
6 | background: linear-gradient(145deg, white, $lightgrey-color);
7 | position: fixed;
8 | top: rem-calc($banner);
9 | left: 0;
10 | width: 100%;
11 | z-index: 3;
12 | transform: translateY(100%);
13 | overflow-y: scroll;
14 |
15 | @include smooth-trans(transform, 0.5s);
16 |
17 | &.open {
18 | transform: translateY(0);
19 | }
20 |
21 | @include laptop {
22 | display: none;
23 | }
24 |
25 | ul {
26 | display: block;
27 | text-align: center;
28 | padding-top: rem-calc(144);
29 | height: 100%;
30 |
31 | @include phablet {
32 | padding-top: rem-calc(144);
33 | }
34 |
35 | li {
36 | margin-bottom: rem-calc(16);
37 |
38 | &:last-of-type {
39 | border-top: 3px solid $snow-color;
40 | padding-top: rem-calc(16);
41 | margin-top: rem-calc(16);
42 | padding-bottom: rem-calc(36);
43 | }
44 |
45 | a {
46 | color: $primary-color;
47 | text-shadow: -5px 0 #ccc, 0 5px #ccc, 5px 0 #ccc, 0 -5px #ccc;
48 | font-weight: 600;
49 | text-transform: lowercase;
50 |
51 | @extend %as-h5;
52 |
53 | &:hover:not(.active-page) {
54 | text-shadow: -15px 0 $lightgrey-color,
55 | 0 15px $lightgrey-color, 15px 0 $lightgrey-color,
56 | 0 -15px $lightgrey-color;
57 | }
58 |
59 | .dots {
60 | color: $secondary-color;
61 | font-size: rem-calc(24);
62 | }
63 |
64 | &.whapp {
65 | @extend %as-h6;
66 |
67 | > svg {
68 | margin-right: 0.5em;
69 | font-size: rem-calc(20);
70 | color: $whapp-color;
71 | }
72 | }
73 |
74 | &.active-page {
75 | text-shadow: -10px 0 $secondary-color,
76 | 0 10px $secondary-color, 10px 0 $secondary-color,
77 | 0 -10px $secondary-color;
78 |
79 | @extend %as-h4;
80 |
81 | .dots {
82 | color: $lightgrey-color;
83 | }
84 | }
85 | }
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/pages/over.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import useSiteMetadata from "../hooks/use-site-metadata";
4 | import useTranslation from "../hooks/use-translation";
5 |
6 | import Layout from "../components/layout";
7 | import SEO from "../components/seo";
8 |
9 | import About from "../components/ui/about";
10 | import Spotify from "../components/ui/spotify";
11 |
12 | import * as aboutStyles from "../styles/modules/pages/about.module.scss";
13 |
14 | // TODO: klaar voor TS'en..
15 |
16 | const AboutPage = () => {
17 | const { t, isHydrated } = useTranslation();
18 |
19 | if (!isHydrated) return null;
20 |
21 | return (
22 |
23 |
24 | {t("about.title")}
25 |
26 |
27 |
33 |
34 | );
35 | };
36 |
37 | export default AboutPage;
38 |
39 | export const Head = () => {
40 | const { siteUrl } = useSiteMetadata();
41 |
42 | const pageTitle = "Over Ons";
43 | const pageDescription =
44 | "Ontdek Menefex, het innovatieve webmediabureau onder leiding van Michael Fransman. Leer meer over onze missie, waarden, en unieke aanpak voor op maat gemaakte digitale oplossingen en webontwikkeling.";
45 | const pageSlug = "/over/";
46 |
47 | const breadcrumbSchema = {
48 | "@context": "https://schema.org",
49 | "@type": "BreadcrumbList",
50 | name: pageTitle,
51 | itemListElement: [
52 | {
53 | "@type": "ListItem",
54 | position: 1,
55 | name: pageTitle,
56 | item: siteUrl + pageSlug,
57 | },
58 | ],
59 | };
60 |
61 | const aboutPageSchema = {
62 | "@context": "https://schema.org",
63 | "@type": "AboutPage",
64 | url: siteUrl + pageSlug,
65 | name: pageTitle,
66 | description: pageDescription,
67 | about: {
68 | "@id": siteUrl + "/#organization",
69 | },
70 | mainEntity: {
71 | "@id": siteUrl + "/#person",
72 | },
73 | isPartOf: {
74 | "@id": siteUrl + "/#webSite",
75 | },
76 | };
77 |
78 | return (
79 |
86 | );
87 | };
88 |
--------------------------------------------------------------------------------
/src/styles/resets.scss:
--------------------------------------------------------------------------------
1 | /* Optimized CSS Reset (2025) - Combines Normalize.css, New CSS Reset, and modern best practices */
2 |
3 | /* 1️⃣ Universal Reset - Resets everything except critical elements */
4 | *:where(:not(html, iframe, canvas, img, svg, video, audio, svg *, symbol *)) {
5 | all: unset;
6 | display: revert;
7 | }
8 |
9 | /* 2️⃣ Box Sizing */
10 | *,
11 | *::before,
12 | *::after {
13 | box-sizing: border-box;
14 | }
15 |
16 | /* 3️⃣ Base Typography & Structure */
17 | html {
18 | font-size: 100%;
19 | text-size-adjust: 100%;
20 | }
21 |
22 | body {
23 | margin: 0;
24 | padding: 0;
25 | background: white;
26 | color: black;
27 | line-height: 1.5;
28 | font-family: system-ui, sans-serif;
29 | }
30 |
31 | /* 4️⃣ Forms & Interactive Elements */
32 | button,
33 | input,
34 | select,
35 | textarea {
36 | font: inherit;
37 | line-height: inherit;
38 | color: inherit;
39 | margin: 0;
40 | }
41 |
42 | button,
43 | a {
44 | cursor: pointer;
45 | }
46 |
47 | button,
48 | select {
49 | text-transform: none;
50 | }
51 |
52 | input,
53 | textarea {
54 | user-select: auto;
55 | border: none;
56 | }
57 |
58 | textarea {
59 | white-space: revert;
60 | overflow: auto;
61 | resize: vertical;
62 | }
63 |
64 | button {
65 | border: none;
66 | background: transparent;
67 | }
68 |
69 | /* 5️⃣ Images & Media */
70 | img,
71 | svg,
72 | video {
73 | max-width: 100%;
74 | height: auto;
75 | border-style: none;
76 | }
77 |
78 | /* 6️⃣ Lists */
79 | ul,
80 | ol,
81 | menu {
82 | list-style: none;
83 | margin: 0;
84 | padding: 0;
85 | }
86 |
87 | /* 7️⃣ Tables */
88 | table {
89 | border-collapse: collapse;
90 | border-spacing: 0;
91 | width: 100%;
92 | }
93 |
94 | caption,
95 | th,
96 | td {
97 | text-align: left;
98 | vertical-align: top;
99 | padding: 0;
100 | }
101 |
102 | /* 8️⃣ Block & Interactive Elements */
103 | main {
104 | display: block;
105 | }
106 |
107 | hr {
108 | height: 0;
109 | box-sizing: content-box;
110 | border: none;
111 | overflow: visible;
112 | }
113 |
114 | pre {
115 | font-family: monospace;
116 | font-size: 1em;
117 | overflow: auto;
118 | }
119 |
120 | /* 9️⃣ Accessibility & Focus */
121 | button:focus,
122 | input:focus,
123 | textarea:focus {
124 | outline: 0 solid transparent; // override 2px
125 | outline-offset: 0; // override 2px
126 | }
127 |
128 | [type="search"] {
129 | appearance: textfield;
130 | outline-offset: -2px;
131 | }
132 |
133 | [type="search"]::-webkit-search-decoration {
134 | appearance: none;
135 | }
136 |
137 | details {
138 | display: block;
139 | }
140 |
141 | summary {
142 | display: list-item;
143 | }
144 |
145 | template {
146 | display: none;
147 | }
148 |
--------------------------------------------------------------------------------
/src/pages/contact.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import useTranslation from "../hooks/use-translation";
4 | import useSiteMetadata from "../hooks/use-site-metadata";
5 |
6 | import Layout from "../components/layout";
7 | import SEO from "../components/seo";
8 |
9 | import LeadForm from "../components/forms/leadForm";
10 | import Info from "../components/ui/info";
11 | import Maps from "../components/google/maps";
12 |
13 | import * as contactStyles from "../styles/modules/pages/contact.module.scss";
14 |
15 | // TODO: klaar voor TS'en..
16 |
17 | const ContactPage = () => {
18 | const { t, isHydrated } = useTranslation();
19 |
20 | if (!isHydrated) return null;
21 |
22 | return (
23 |
24 |
28 |
29 |
35 |
36 |
37 |
38 | );
39 | };
40 |
41 | export default ContactPage;
42 |
43 | export const Head = () => {
44 | const { siteUrl } = useSiteMetadata();
45 |
46 | const pageTitle = "Contact";
47 | const pageDescription =
48 | "Contacteer Menefex voor een offerte, samenwerking of algemene vragen. Wij staan klaar om samen iets geweldigs te creëren! Binnen één werkdag reactie.";
49 | const pageSlug = "/contact/";
50 |
51 | const breadcrumbSchema = {
52 | "@context": "https://schema.org",
53 | "@type": "BreadcrumbList",
54 | name: pageTitle,
55 | itemListElement: [
56 | {
57 | "@type": "ListItem",
58 | position: 1,
59 | name: pageTitle,
60 | item: siteUrl + pageSlug,
61 | },
62 | ],
63 | };
64 |
65 | const contactPageSchema = {
66 | "@context": "https://schema.org",
67 | "@type": "ContactPage",
68 | url: siteUrl + pageSlug,
69 | name: pageTitle,
70 | description: pageDescription,
71 | about: {
72 | "@id": siteUrl + "/#organization",
73 | },
74 | mainEntity: {
75 | "@id": siteUrl + "/#organization",
76 | },
77 | isPartOf: {
78 | "@id": siteUrl + "/#webSite",
79 | },
80 | };
81 |
82 | return (
83 |
90 | );
91 | };
92 |
--------------------------------------------------------------------------------
/src/styles/modules/ui/actual.module.scss:
--------------------------------------------------------------------------------
1 | @use "../../customs" as *;
2 |
3 | .actual {
4 | $actual: &;
5 |
6 | text-align: center;
7 |
8 | > span {
9 | margin-bottom: rem-calc(36);
10 | display: flex;
11 | align-items: center;
12 | flex-wrap: wrap;
13 | justify-content: center;
14 | gap: rem-calc(12 16);
15 |
16 | > h3 {
17 | @include section-title(center, $space: rem-calc(0));
18 | }
19 |
20 | > a {
21 | @include more-button;
22 | }
23 | }
24 |
25 | &-container {
26 | display: flex;
27 | gap: rem-calc(48);
28 | flex-wrap: wrap;
29 | justify-content: center;
30 |
31 | @include container($pt: 0, $pb: 0);
32 |
33 | #{$actual}-wrapper {
34 | display: flex;
35 | flex-direction: column;
36 | align-items: center;
37 | flex: 1;
38 | gap: rem-calc(24);
39 | min-width: rem-calc(320);
40 | max-width: rem-calc(400);
41 |
42 | #{$actual}-image {
43 | > a {
44 | [data-gatsby-image-wrapper] {
45 | // max-height: rem-calc(235);
46 | aspect-ratio: 16 / 10;
47 | width: 100%;
48 | height: 100%;
49 | border-radius: $low-radius;
50 |
51 | img {
52 | @include smooth-trans;
53 | }
54 | }
55 | }
56 | }
57 |
58 | #{$actual}-content {
59 | display: flex;
60 | flex-direction: column;
61 | text-align: center;
62 | gap: rem-calc(18);
63 |
64 | > h4 {
65 | @include clamp(2);
66 | @include fluid-typing(18, 21);
67 | }
68 |
69 | > div {
70 | > time {
71 | color: $grey-color;
72 | pointer-events: none;
73 | }
74 |
75 | > a {
76 | @include text-link;
77 | }
78 | }
79 | }
80 |
81 | > ul {
82 | display: flex;
83 | flex-wrap: wrap;
84 | justify-content: center;
85 | gap: rem-calc(16 12);
86 |
87 | > li > a {
88 | @include topic-button;
89 | }
90 | }
91 |
92 | &:hover {
93 | #{$actual}-image {
94 | img {
95 | transform: scale(1.05);
96 | }
97 | }
98 | }
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/deno.lock:
--------------------------------------------------------------------------------
1 | {
2 | "version": "5",
3 | "remote": {
4 | "https://edge.netlify.com/": "fd941d61d88673d5f28aab283fb86fcc50f08a3bc80ee5470498fcfa88c65cfb",
5 | "https://edge.netlify.com/bootstrap/config.ts": "6a2ce0e544e15e8f8883a5c18da5948e37fd0f2619f68cb31f3af53c51817025",
6 | "https://edge.netlify.com/bootstrap/context.ts": "72496497b40d9e808f419efc764ecb438952a32f61ed94cd54952fc59f17f69d",
7 | "https://edge.netlify.com/bootstrap/cookie.ts": "8b0baae708989ca183c6f3b4ab3d029e6abcbc2e43f93edeb0ff447b3bbc3a05",
8 | "https://edge.netlify.com/bootstrap/edge_function.ts": "b8253e86aa83c67341f5cfedeba5049d77fbf84dcab7eceff7566b7728ae9b39",
9 | "https://edge.netlify.com/bootstrap/globals/types.ts": "eaa6148ded3121d8dee62dd91c86e7fe76601df0f3ca8d7962243a30f4c8935f"
10 | },
11 | "workspace": {
12 | "packageJson": {
13 | "dependencies": [
14 | "npm:@contentful/live-preview@^4.7.0",
15 | "npm:@contentful/rich-text-html-renderer@17.1.6",
16 | "npm:@contentful/rich-text-react-renderer@16.1.6",
17 | "npm:@contentful/rich-text-types@17.2.5",
18 | "npm:@fortawesome/fontawesome-svg-core@^7.1.0",
19 | "npm:@fortawesome/free-brands-svg-icons@^7.1.0",
20 | "npm:@fortawesome/free-regular-svg-icons@^7.1.0",
21 | "npm:@fortawesome/free-solid-svg-icons@^7.1.0",
22 | "npm:@fortawesome/react-fontawesome@^3.1.1",
23 | "npm:@sendgrid/mail@^8.1.6",
24 | "npm:axios@^1.13.2",
25 | "npm:eslint-config-airbnb@^19.0.4",
26 | "npm:eslint-plugin-import@^2.32.0",
27 | "npm:eslint-plugin-jsx-a11y@^6.10.2",
28 | "npm:eslint-plugin-react-hooks@^7.0.1",
29 | "npm:eslint-plugin-react@^7.37.5",
30 | "npm:eslint@^9.39.1",
31 | "npm:gatsby-adapter-netlify@^1.3.0",
32 | "npm:gatsby-plugin-canonical-urls@^5.15.0",
33 | "npm:gatsby-plugin-catch-links@^5.15.0",
34 | "npm:gatsby-plugin-disqus@^1.2.6",
35 | "npm:gatsby-plugin-feed@^5.15.0",
36 | "npm:gatsby-plugin-google-tagmanager@^5.15.0",
37 | "npm:gatsby-plugin-image@^3.15.0",
38 | "npm:gatsby-plugin-manifest@^5.15.0",
39 | "npm:gatsby-plugin-nprogress@^5.15.0",
40 | "npm:gatsby-plugin-offline@^6.15.0",
41 | "npm:gatsby-plugin-robots-txt@^1.8.0",
42 | "npm:gatsby-plugin-sass@^6.15.0",
43 | "npm:gatsby-plugin-sharp@^5.15.0",
44 | "npm:gatsby-plugin-sitemap@^6.15.0",
45 | "npm:gatsby-source-contentful@^8.16.0",
46 | "npm:gatsby-source-filesystem@^5.15.0",
47 | "npm:gatsby-transformer-sharp@^5.15.0",
48 | "npm:gatsby@^5.15.0",
49 | "npm:google-map-react@^2.2.5",
50 | "npm:i18next-browser-languagedetector@^8.2.0",
51 | "npm:i18next-http-backend@^3.0.2",
52 | "npm:i18next@^25.7.1",
53 | "npm:puppeteer@^24.31.0",
54 | "npm:react-cookie-consent@9",
55 | "npm:react-dom@^18.3.1",
56 | "npm:react-i18next@^16.3.5",
57 | "npm:react@^18.3.1",
58 | "npm:sass@^1.94.2",
59 | "npm:stylelint-config-prettier-scss@1",
60 | "npm:stylelint-config-standard-scss@16",
61 | "npm:stylelint@^16.26.1",
62 | "npm:swiper@^12.0.3"
63 | ]
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "menefex",
3 | "private": true,
4 | "description": "Wij bouwen websites & webapps bouwen met oog voor detail.",
5 | "version": "1.8.7",
6 | "author": "Michael Fransman ",
7 | "license": "agpl-3.0",
8 | "scripts": {
9 | "build": "gatsby clean && gatsby build",
10 | "develop": "gatsby develop -H 0.0.0.0 --open",
11 | "start": "npm run develop",
12 | "clean": "gatsby clean && yarn cache clean",
13 | "reinstall": "rm -rf node_modules package-lock.json yarn.lock && yarn",
14 | "serve": "gatsby serve",
15 | "format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,md,css}\"",
16 | "eslint-check": "eslint --print-config .eslintrc | eslint-config-prettier-check",
17 | "test": "echo \"Write tests! -> https://gatsby.dev/unit-testing\" && exit 1",
18 | "prebuild": "yarn install --frozen-lockfile",
19 | "postinstall": "if [ \"$NETLIFY\" = \"true\" ]; then node node_modules/puppeteer/install.mjs; fi"
20 | },
21 | "dependencies": {
22 | "@contentful/live-preview": "^4.9.0",
23 | "@contentful/rich-text-html-renderer": "17.1.6",
24 | "@contentful/rich-text-react-renderer": "16.1.6",
25 | "@contentful/rich-text-types": "17.2.5",
26 | "@fortawesome/fontawesome-svg-core": "^7.1.0",
27 | "@fortawesome/free-brands-svg-icons": "^7.1.0",
28 | "@fortawesome/free-regular-svg-icons": "^7.1.0",
29 | "@fortawesome/free-solid-svg-icons": "^7.1.0",
30 | "@fortawesome/react-fontawesome": "^3.1.1",
31 | "@sendgrid/mail": "^8.1.6",
32 | "axios": "^1.13.2",
33 | "gatsby": "^5.15.0",
34 | "gatsby-adapter-netlify": "^1.3.0",
35 | "gatsby-plugin-canonical-urls": "^5.15.0",
36 | "gatsby-plugin-catch-links": "^5.15.0",
37 | "gatsby-plugin-disqus": "^1.2.6",
38 | "gatsby-plugin-feed": "^5.15.0",
39 | "gatsby-plugin-google-tagmanager": "^5.15.0",
40 | "gatsby-plugin-image": "^3.15.0",
41 | "gatsby-plugin-manifest": "^5.15.0",
42 | "gatsby-plugin-nprogress": "^5.15.0",
43 | "gatsby-plugin-offline": "^6.15.0",
44 | "gatsby-plugin-robots-txt": "^1.8.0",
45 | "gatsby-plugin-sass": "^6.15.0",
46 | "gatsby-plugin-sharp": "^5.15.0",
47 | "gatsby-plugin-sitemap": "^6.15.0",
48 | "gatsby-source-contentful": "^8.16.0",
49 | "gatsby-source-filesystem": "^5.15.0",
50 | "gatsby-transformer-sharp": "^5.15.0",
51 | "google-map-react": "^2.2.5",
52 | "i18next": "^25.7.3",
53 | "i18next-browser-languagedetector": "^8.2.0",
54 | "i18next-http-backend": "^3.0.2",
55 | "puppeteer": "^24.34.0",
56 | "react": "^18.3.1",
57 | "react-cookie-consent": "^9.0.0",
58 | "react-dom": "^18.3.1",
59 | "react-i18next": "^16.5.0",
60 | "sass": "^1.97.0",
61 | "swiper": "^12.0.3"
62 | },
63 | "devDependencies": {
64 | "eslint": "^9.39.2",
65 | "eslint-config-airbnb": "^19.0.4",
66 | "eslint-plugin-import": "^2.32.0",
67 | "eslint-plugin-jsx-a11y": "^6.10.2",
68 | "eslint-plugin-react": "^7.37.5",
69 | "eslint-plugin-react-hooks": "^7.0.1",
70 | "stylelint": "^16.26.1",
71 | "stylelint-config-prettier-scss": "^1.0.0",
72 | "stylelint-config-standard-scss": "^16.0.0"
73 | },
74 | "homepage": "https://menefex.nl",
75 | "keywords": [
76 | "menefex"
77 | ],
78 | "repository": {
79 | "type": "git",
80 | "url": "https://github.com/mikeyfe6/menefex"
81 | },
82 | "bugs": {
83 | "url": "https://github.com/gatsbyjs/gatsby/issues"
84 | },
85 | "packageManager": "yarn@1.22.22",
86 | "browserslist": [
87 | "last 2 Chrome versions",
88 | "last 2 Firefox versions",
89 | "last 2 Edge versions",
90 | "last 2 Safari versions"
91 | ]
92 | }
93 |
--------------------------------------------------------------------------------
/src/styles/layout.scss:
--------------------------------------------------------------------------------
1 | @use "./customs" as *;
2 | @use "cookie";
3 | @use "forms";
4 | @use "globals";
5 | @use "page";
6 | @use "swiper";
7 | @use "typography";
8 |
9 | :root {
10 | --swiper-pagination-bullet-size: 10px;
11 | --swiper-theme-color: #{$secondary-color} !important;
12 | }
13 |
14 | html {
15 | scroll-behavior: smooth;
16 | }
17 |
18 | body {
19 | font-family: $poppins-font;
20 | color: $primary-color;
21 | line-height: 1.6;
22 | min-width: rem-calc(320);
23 |
24 | &:has(.cookie),
25 | &:has(#backdrop) {
26 | overflow: hidden;
27 | }
28 | }
29 |
30 | .menefex {
31 | display: flex;
32 | flex-direction: column;
33 | min-height: 100vh;
34 | }
35 |
36 | main {
37 | flex: 1;
38 | }
39 |
40 | footer {
41 | flex-shrink: 0;
42 | }
43 |
44 | section {
45 | &:only-child {
46 | margin-bottom: rem-calc(80);
47 |
48 | @include phablet {
49 | margin-bottom: rem-calc(120);
50 | }
51 |
52 | @include tablet {
53 | margin-bottom: rem-calc(160);
54 | }
55 | }
56 |
57 | &:not(.page-intro, #notes, #about, #spotify, #info, #blogtemplate) {
58 | padding: rem-calc(48 0);
59 |
60 | @include phablet {
61 | padding: rem-calc(60 0);
62 | }
63 |
64 | @include tablet {
65 | padding: rem-calc(80 0);
66 | }
67 |
68 | &:last-child {
69 | padding-bottom: rem-calc(120);
70 |
71 | @include phablet {
72 | padding-bottom: rem-calc(180);
73 | }
74 |
75 | @include tablet {
76 | padding-bottom: rem-calc(220);
77 | }
78 | }
79 | }
80 |
81 | #cta {
82 | scroll-margin-top: rem-calc(200);
83 |
84 | @include phablet {
85 | scroll-margin-top: rem-calc(215);
86 | }
87 |
88 | @include laptop {
89 | scroll-margin-top: rem-calc(225);
90 | }
91 |
92 | @include desktop {
93 | scroll-margin-top: rem-calc(230);
94 | }
95 | }
96 |
97 | projects,
98 | services,
99 | biography {
100 | scroll-margin-top: rem-calc(115);
101 |
102 | @include phablet {
103 | scroll-margin-top: rem-calc(130);
104 | }
105 |
106 | @include laptop {
107 | scroll-margin-top: rem-calc(140);
108 | }
109 |
110 | @include desktop {
111 | scroll-margin-top: rem-calc(145);
112 | }
113 | }
114 |
115 | services {
116 | margin: rem-calc(96 0);
117 |
118 | @include phablet {
119 | margin: rem-calc(120 0);
120 | }
121 |
122 | @include tablet {
123 | margin: rem-calc(120 0 192);
124 | }
125 | }
126 | }
127 |
128 | /* Width */
129 | ::-webkit-scrollbar {
130 | width: 7.5px;
131 | }
132 |
133 | /* Track */
134 | ::-webkit-scrollbar-track {
135 | background-color: $secondary-color;
136 | }
137 |
138 | /* Handle */
139 | ::-webkit-scrollbar-thumb {
140 | background-color: $primary-color;
141 | }
142 |
143 | /* Handle on hover */
144 | ::-webkit-scrollbar-thumb:hover {
145 | background-color: $grey-color;
146 | }
147 |
148 | /* Selection */
149 | ::selection {
150 | color: $dark-color;
151 | background-color: $secondary-color;
152 | }
153 |
154 | .grecaptcha-badge {
155 | visibility: hidden;
156 | }
157 |
158 | .responsive-tag {
159 | position: fixed;
160 | top: 1%;
161 | left: 5px;
162 | padding: rem-calc(2 8);
163 | z-index: 1000;
164 | opacity: 0.5;
165 | color: $dark-color;
166 | background-color: $white-color;
167 | outline: $dark-color 1px solid;
168 | text-align: center;
169 |
170 | p {
171 | font-size: rem-calc(12);
172 | font-weight: bold;
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/src/styles/modules/ui/faq.module.scss:
--------------------------------------------------------------------------------
1 | @use "../../customs" as *;
2 |
3 | .faq {
4 | $faq: &;
5 |
6 | &-container {
7 | @include container;
8 |
9 | > div {
10 | display: flex;
11 | flex-direction: column;
12 | gap: rem-calc(32);
13 |
14 | @include phablet {
15 | flex-direction: row;
16 | }
17 |
18 | #{$faq}-image {
19 | max-width: rem-calc(450);
20 | width: 100%;
21 | margin: 0 auto;
22 | background-color: $smoke-color;
23 | border-radius: $high-radius;
24 |
25 | @include phablet {
26 | min-width: rem-calc(300);
27 | margin: unset;
28 | }
29 |
30 | img {
31 | border-radius: $high-radius;
32 | filter: opacity(0.9) brightness(1.1);
33 |
34 | @include border($white-color, 4px);
35 | }
36 | }
37 |
38 | #{$faq}-content {
39 | > span {
40 | margin-bottom: rem-calc(36);
41 | display: flex;
42 | align-items: center;
43 | flex-wrap: wrap;
44 | gap: rem-calc(12 16);
45 |
46 | > h3 {
47 | @include section-title($space: rem-calc(0));
48 |
49 | @extend %as-h2;
50 | }
51 |
52 | > a {
53 | @include more-button;
54 | }
55 | }
56 |
57 | #{$faq}-item {
58 | margin-bottom: rem-calc(8);
59 |
60 | #{$faq}-question {
61 | padding-bottom: rem-calc(12);
62 |
63 | > h4 {
64 | @extend %as-h6;
65 | @include smooth-trans;
66 | @include underline($color: $smoke-color);
67 | }
68 |
69 | &:hover {
70 | > h4 {
71 | text-decoration-color: $link-color;
72 | }
73 | }
74 | }
75 |
76 | #{$faq}-answer {
77 | max-height: 0;
78 | opacity: 0;
79 | overflow: hidden;
80 | transition: max-height 0.4s cubic-bezier(0.4, 0, 0.2, 1),
81 | opacity 0.4s;
82 | display: flex;
83 | gap: rem-calc(12);
84 |
85 | > svg {
86 | color: $secondary-color;
87 | padding-top: rem-calc(2);
88 |
89 | @include mobile {
90 | padding-top: rem-calc(3);
91 | }
92 | }
93 |
94 | > p {
95 | padding-bottom: rem-calc(16);
96 |
97 | @extend %as-medium;
98 |
99 | a {
100 | @include text-link;
101 | }
102 | }
103 |
104 | &.open {
105 | max-height: rem-calc(200);
106 | opacity: 1;
107 | transition: max-height 0.5s
108 | cubic-bezier(0.4, 0, 0.2, 1),
109 | opacity 0.5s;
110 | }
111 | }
112 | }
113 | }
114 | }
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/src/styles/_page.scss:
--------------------------------------------------------------------------------
1 | @use "./customs" as *;
2 |
3 | .page {
4 | $page: &;
5 |
6 | &-banner {
7 | display: flex;
8 | justify-content: center;
9 | align-items: center;
10 | background-color: $dark-color;
11 | color: $white-color;
12 | height: rem-calc($banner);
13 |
14 | .banner {
15 | overflow: clip;
16 | mask-image: linear-gradient(
17 | to right,
18 | transparent 0%,
19 | black 10%,
20 | black 90%,
21 | transparent 100%
22 | );
23 |
24 | @include container;
25 |
26 | &-text {
27 | display: flex;
28 | gap: rem-calc(12);
29 | padding-left: rem-calc(12);
30 | animation: scroll-left 25s linear infinite;
31 | width: max-content;
32 |
33 | p {
34 | @include fluid-typing(14, 16);
35 |
36 | a {
37 | @include text-link;
38 | }
39 |
40 | &::after {
41 | content: "✨";
42 | margin-left: rem-calc(12);
43 | }
44 | }
45 |
46 | &:hover {
47 | animation-play-state: paused;
48 | }
49 | }
50 | }
51 | }
52 |
53 | &-intro {
54 | @include container($mt: rem-calc(32));
55 |
56 | h1 {
57 | font-family: $wdxl-font;
58 | line-height: rem-calc(40);
59 |
60 | &:not(:only-child) {
61 | margin-bottom: rem-calc(20);
62 | }
63 |
64 | &::after {
65 | content: ".";
66 | color: $secondary-color;
67 | }
68 | }
69 |
70 | h2 {
71 | font-weight: 500;
72 | font-family: $poppins-font;
73 | letter-spacing: $low-spacing;
74 | max-width: 70ch;
75 |
76 | @include fluid-typing(19, 21);
77 | }
78 |
79 | &.contact {
80 | h2 {
81 | display: flex;
82 | flex-direction: column;
83 | gap: rem-calc(16);
84 |
85 | span {
86 | display: block;
87 | }
88 | }
89 | }
90 |
91 | &.about {
92 | h1 {
93 | margin-bottom: 0;
94 | }
95 | }
96 |
97 | #{$page}-link {
98 | @include text-link;
99 | }
100 |
101 | &.success {
102 | @include container($mt: rem-calc(32), $mb: rem-calc(64));
103 | }
104 | }
105 |
106 | &-docs {
107 | @include container;
108 |
109 | /* stylelint-disable-next-line no-descending-specificity */
110 | h1 {
111 | margin-bottom: rem-calc(24);
112 | line-height: rem-calc(48);
113 |
114 | @extend %as-h3;
115 | }
116 |
117 | h5 {
118 | margin-top: rem-calc(32);
119 | }
120 |
121 | p {
122 | margin: rem-calc(16 0);
123 | }
124 |
125 | a {
126 | @include text-link;
127 | }
128 |
129 | ol,
130 | ul {
131 | margin: rem-calc(16 0);
132 | padding-left: rem-calc(20);
133 | }
134 |
135 | ol {
136 | list-style-type: decimal;
137 | }
138 |
139 | ul {
140 | list-style-type: disc;
141 | }
142 | }
143 |
144 | &-offset {
145 | display: block;
146 | width: 100%;
147 | height: rem-calc(85);
148 |
149 | @include phablet {
150 | height: rem-calc(100);
151 | }
152 |
153 | @include laptop {
154 | height: rem-calc(110);
155 | }
156 |
157 | @include desktop {
158 | height: rem-calc(115);
159 | }
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/static/llms.txt:
--------------------------------------------------------------------------------
1 | # #1 Website Specialist · Menefex
2 |
3 | Menefex is een vooraanstaand & innovatief webmediabureau in Amsterdam, opgericht door Michael Fransman. Wij ontwerpen, ontwikkelen en optimaliseren op maat gemaakte websites, webshops en webapplicaties. Vanaf €295,- jouw digitale ambities waarmaken. Neem vandaag nog contact op!
4 |
5 | ## About
6 |
7 | - [Over Menefex](https://menefex.nl/over/): Informatie over de oprichter Michael Fransman en de missie van Menefex als modern webmediabureau. Inclusief persoonlijke Spotify playlist en bedrijfsdoelen.
8 | ## Services
9 |
10 | - [Diensten](https://menefex.nl/diensten/): Overzicht van alle diensten die Menefex aanbiedt
11 | - [Website laten maken](https://menefex.nl/diensten/website-laten-maken/): Op maat gemaakte websites met uniek design, snelle laadtijden en SEO-optimalisatie
12 | - [Webapplicatie laten maken](https://menefex.nl/diensten/webapplicatie-laten-maken/): Professionele webapplicaties voor bedrijfsprocessen en efficiëntie
13 | - [Webshop laten maken](https://menefex.nl/diensten/webshop-laten-maken/): Complete e-commerce oplossingen voor online verkoop
14 | - [Zoekmachineoptimalisatie (SEO)](https://menefex.nl/diensten/zoekmachineoptimalisatie/): Verbeter je vindbaarheid in Google en andere zoekmachines
15 | - [Onderhoud & Updates](https://menefex.nl/diensten/onderhoud-updates-uitvoeren/): Regelmatig onderhoud voor optimale prestaties en veiligheid
16 | - [Website Optimalisaties](https://menefex.nl/diensten/optimalisaties-laten-uitvoeren/): Prestaties, snelheid en conversie verbeteren
17 | - [E-mail Templates](https://menefex.nl/diensten/email-template-laten-maken/): Professionele e-mail templates die passen bij je branding
18 |
19 | ## Pricing Packages
20 |
21 | - [Prijzen & tarieven](https://menefex.nl/prijzen/): Transparante prijsstructuur met vier vaste pakketten:
22 | - **Budget Plan** (€295): Voor kleine projecten, 3-5 pagina's, maatwerk design, 1 maand gratis support
23 | - **Starter Plan** (€595): Voor groeiende bedrijven, meer pagina's, blog functie, 3 maanden gratis support
24 | - **Established Plan** (€1025): Voor gevestigde bedrijven, uitgebreid pakket, 6 maanden gratis support
25 | - **Business Plan** (€1575): Voor grote bedrijven/e-commerce, onbeperkte pagina's, 12 maanden gratis support
26 | - Uurtarief: €45 per uur (excl. BTW) voor maatwerk en kleinere taken
27 |
28 | ## Technology & Approach
29 |
30 | - **Tech Stack**: GatsbyJS, ReactJS, Netlify, Contentful, WordPress waar nodig
31 | - **Focus**: Functionaliteit in balans met moderne design
32 | - **Methodiek**: Maatwerk in plaats van templates, persoonlijke begeleiding
33 | - **Service**: Hulp en communicatie vóór, tijdens en ná het project
34 |
35 | ## Portfolio
36 |
37 | - [Portfolio](https://menefex.nl/portfolio/): Recente projecten inclusief:
38 | - Edutain U Productions (cultureel bedrijf)
39 | - Prio Zorg (hulpverlening voor jongeren)
40 | - Keep It Real (jongeren programma)
41 | - Afrodiasphere (eigen initiatief, sociale platform)
42 | - Eternity Percussion (culturele instelling)
43 | - DS Melodies (steeldrum artiest)
44 | - Black Harmony (zanggroep)
45 |
46 | ## Business Information
47 |
48 | - **KvK nummer**: 76045315
49 | - **BTW nummer**: NL003040579B17
50 | - **Locatie**: Amsterdam-Zuidoost, Nederland
51 | - **Servicegebied**: Nederland (NL), België (BE), Suriname (SR), Groot-Brittannië (GB)
52 | - **Talen**: Nederlands, Engels, Spaans, Duits
53 | - **Contact**: +31 6 11 05 43 18, michaelfransman@menefex.nl
54 |
55 | ## Privacy and Terms
56 |
57 | - [Privacybeleid](https://menefex.nl/privacybeleid/): GDPR-compliant privacybeleid met uitleg over cookies en gegevensbescherming
58 | - [Algemene Voorwaarden](https://menefex.nl/algemene-voorwaarden/): Uitgebreide voorwaarden voor dienstverlening, aansprakelijkheid en intellectueel eigendom
59 |
60 | ## Support
61 |
62 | - [Contact](https://menefex.nl/contact/): Contactformulier, bedrijfsinformatie en directe communicatielijnen
63 | - [FAQ](https://menefex.nl/faq/): Veelgestelde vragen over proces, prijzen, onderhoud en meer
64 |
65 | ## Content & Updates
66 |
67 | - [Blog](https://menefex.nl/blog/): Artikelen over webdesign, ontwikkeling, digitale trends en bedrijfsupdates
--------------------------------------------------------------------------------
/src/pages/diensten/analyse-laten-uitvoeren.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import { Link } from "gatsby";
4 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
5 |
6 | import useTranslation from "../../hooks/use-translation";
7 | import useSiteMetadata from "../../hooks/use-site-metadata";
8 |
9 | import Layout from "../../components/layout";
10 | import SEO from "../../components/seo";
11 |
12 | import * as servicesStyles from "../../styles/modules/pages/services.module.scss";
13 |
14 | // TODO: klaar voor TS'en..
15 |
16 | const AnalysePage = () => {
17 | const { t, isHydrated } = useTranslation();
18 |
19 | if (!isHydrated) return null;
20 |
21 | return (
22 |
23 |
24 | {t("services.analyse.title")}
25 | {t("services.analyse.intro")}
26 |
27 |
28 |
29 |
30 |
36 |
37 |
38 |
39 | {" "}
40 | {t("services.allServices")}
41 |
42 |
43 | {t("services.goToPrices")}{" "}
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | );
55 | };
56 |
57 | export default AnalysePage;
58 |
59 | export const Head = () => {
60 | const { siteUrl } = useSiteMetadata();
61 |
62 | const pageTitle = "Analyse laten uitvoeren";
63 | const pageDescription =
64 | "Professionele analyse diensten om inzicht te krijgen in jouw bedrijfsprocessen en online prestaties, zodat je weloverwogen beslissingen kunt nemen.";
65 | const pageSlug = "/diensten/analyse-laten-uitvoeren/";
66 |
67 | const breadcrumbSchema = {
68 | "@context": "https://schema.org",
69 | "@type": "BreadcrumbList",
70 | name: pageTitle,
71 | itemListElement: [
72 | {
73 | "@type": "ListItem",
74 | position: 1,
75 | name: "Diensten",
76 | item: siteUrl + "/diensten/",
77 | },
78 | {
79 | "@type": "ListItem",
80 | position: 2,
81 | name: pageTitle,
82 | item: siteUrl + pageSlug,
83 | },
84 | ],
85 | };
86 |
87 | const serviceSchema = {
88 | "@context": "https://schema.org",
89 | "@type": "Service",
90 | name: pageTitle,
91 | description: pageDescription,
92 | provider: {
93 | "@id": siteUrl + "/#organization",
94 | },
95 | serviceType: pageTitle,
96 | category: "Web Development",
97 | areaServed: {
98 | "@type": "Country",
99 | name: "Netherlands",
100 | },
101 | };
102 |
103 | return (
104 |
111 | );
112 | };
113 |
--------------------------------------------------------------------------------
/src/styles/modules/layout/desktopMenu.module.scss:
--------------------------------------------------------------------------------
1 | @use "../../customs" as *;
2 |
3 | .desktop-menu {
4 | width: 100%;
5 | top: rem-calc($banner);
6 | left: 0;
7 | position: fixed;
8 | background: linear-gradient(
9 | to bottom,
10 | $white-color,
11 | rgba($white-color, 90%)
12 | );
13 | padding: rem-calc(16 0);
14 | z-index: 7;
15 | border-top: 7.5px solid $secondary-color;
16 | border-bottom: 1px solid rgba($white-color, 100%);
17 |
18 | @include smooth-trans(top, 0.1s);
19 |
20 | &[data-scrolled="true"] {
21 | top: 0 !important;
22 |
23 | @include smooth-trans(top, 0.1s);
24 | }
25 |
26 | @include phablet {
27 | padding: rem-calc(20 0);
28 | }
29 |
30 | @include laptop {
31 | padding: rem-calc(24 0);
32 | }
33 |
34 | > nav {
35 | display: flex;
36 | align-items: center;
37 | justify-content: space-between;
38 | height: 100%;
39 |
40 | @include container($pt: rem-calc(0), $pb: rem-calc(0));
41 |
42 | .logo {
43 | display: flex;
44 | width: rem-calc(175);
45 |
46 | @include mobile {
47 | width: rem-calc(200);
48 | }
49 |
50 | @include phablet {
51 | width: rem-calc(250);
52 | }
53 | }
54 |
55 | > ul {
56 | display: none;
57 | align-items: center;
58 | gap: rem-calc(16);
59 | border: 2px solid rgba($dark-color, 0.75);
60 | padding: rem-calc(0 16);
61 | border-radius: $medium-radius;
62 |
63 | @include laptop {
64 | display: flex;
65 | }
66 |
67 | > li {
68 | &:has(.whapp) {
69 | padding: rem-calc(0 12);
70 | border-left: 2px solid rgba($dark-color, 0.75);
71 | border-right: 2px solid rgba($dark-color, 0.75);
72 | }
73 |
74 | > a {
75 | font-weight: 600;
76 | font-family: $wdxl-font;
77 | text-transform: lowercase;
78 |
79 | @extend %as-h5;
80 |
81 | &:hover {
82 | color: $dark-color;
83 | }
84 |
85 | > span {
86 | color: $secondary-color;
87 | }
88 |
89 | &.whapp {
90 | font-size: rem-calc(28);
91 |
92 | &:hover {
93 | color: $whapp-color;
94 | }
95 | }
96 |
97 | &.active-page {
98 | color: $dark-color;
99 | }
100 | }
101 | }
102 | }
103 | }
104 |
105 | .language {
106 | display: flex;
107 | align-items: center;
108 | justify-content: center;
109 | gap: rem-calc(8);
110 |
111 | > button {
112 | width: rem-calc(28);
113 | height: rem-calc(28);
114 | display: flex;
115 | align-items: center;
116 | justify-content: center;
117 | border-radius: $low-radius;
118 | border: 2px solid rgba($lightgrey-color, 1);
119 | aspect-ratio: 1;
120 |
121 | @extend %as-tiny;
122 |
123 | &:hover {
124 | background-color: $lemon-color;
125 | }
126 |
127 | &.active {
128 | background-color: $primary-color;
129 | color: $white-color;
130 | pointer-events: none;
131 | }
132 | }
133 |
134 | > span {
135 | font-size: rem-calc(24);
136 | }
137 | }
138 |
139 | .lang-and-hamburger {
140 | display: flex;
141 | align-items: center;
142 | gap: rem-calc(24);
143 |
144 | @include laptop {
145 | display: none;
146 | }
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/src/pages/diensten/email-template-laten-maken.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import { Link } from "gatsby";
4 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
5 |
6 | import useTranslation from "../../hooks/use-translation";
7 | import useSiteMetadata from "../../hooks/use-site-metadata";
8 |
9 | import Layout from "../../components/layout";
10 | import SEO from "../../components/seo";
11 |
12 | import * as servicesStyles from "../../styles/modules/pages/services.module.scss";
13 |
14 | // TODO: klaar voor TS'en..
15 |
16 | const EmailTemplatePage = () => {
17 | const { t, isHydrated } = useTranslation();
18 |
19 | if (!isHydrated) return null;
20 |
21 | return (
22 |
23 |
24 | {t("services.email.title")}
25 | {t("services.email.intro")}
26 |
27 |
28 |
29 |
30 |
36 |
37 |
38 |
39 | {" "}
40 | {t("services.allServices")}
41 |
42 |
43 | {t("services.goToPrices")}{" "}
44 |
45 |
46 |
47 |
48 |
49 |
52 |
53 |
54 |
55 |
56 | );
57 | };
58 |
59 | export default EmailTemplatePage;
60 |
61 | export const Head = () => {
62 | const { siteUrl } = useSiteMetadata();
63 |
64 | const pageTitle = "E-mailtemplate laten maken";
65 | const pageDescription =
66 | "Op maat gemaakte e-mail templates die passen bij jouw branding en zorgen voor een consistente en professionele uitstraling in al je e-mailcommunicatie.";
67 | const pageSlug = "/diensten/email-template-laten-maken/";
68 |
69 | const breadcrumbSchema = {
70 | "@context": "https://schema.org",
71 | "@type": "BreadcrumbList",
72 | name: pageTitle,
73 | itemListElement: [
74 | {
75 | "@type": "ListItem",
76 | position: 1,
77 | name: "Diensten",
78 | item: siteUrl + "/diensten/",
79 | },
80 | {
81 | "@type": "ListItem",
82 | position: 2,
83 | name: pageTitle,
84 | item: siteUrl + pageSlug,
85 | },
86 | ],
87 | };
88 |
89 | const serviceSchema = {
90 | "@context": "https://schema.org",
91 | "@type": "Service",
92 | name: pageTitle,
93 | description: pageDescription,
94 | provider: {
95 | "@id": siteUrl + "/#organization",
96 | },
97 | serviceType: pageTitle,
98 | category: "Web Development",
99 | areaServed: {
100 | "@type": "Country",
101 | name: "Netherlands",
102 | },
103 | };
104 |
105 | return (
106 |
113 | );
114 | };
115 |
--------------------------------------------------------------------------------
/src/pages/diensten/website-laten-maken.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import { Link } from "gatsby";
4 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
5 |
6 | import useTranslation from "../../hooks/use-translation";
7 | import useSiteMetadata from "../../hooks/use-site-metadata";
8 |
9 | import Layout from "../../components/layout";
10 | import SEO from "../../components/seo";
11 |
12 | import * as servicesStyles from "../../styles/modules/pages/services.module.scss";
13 |
14 | // TODO: klaar voor TS'en..
15 |
16 | const WebsitePage = () => {
17 | const { t, isHydrated } = useTranslation();
18 |
19 | if (!isHydrated) return null;
20 |
21 | return (
22 |
23 |
24 |
29 | {t("services.websites.intro")}
30 |
31 |
32 |
33 |
34 |
40 |
41 |
42 |
43 | {" "}
44 | {t("services.allServices")}
45 |
46 |
47 | {t("services.goToPrices")}{" "}
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | );
59 | };
60 |
61 | export default WebsitePage;
62 |
63 | export const Head = () => {
64 | const { siteUrl } = useSiteMetadata();
65 |
66 | const pageTitle = "Website laten maken";
67 | const pageDescription =
68 | "Op maat gemaakte websites die perfect aansluiten bij jouw branding en doelstellingen. Wij ontwerpen professionele, gebruiksvriendelijke websites die zorgen voor een sterke online aanwezigheid en optimale gebruikerservaring.";
69 | const pageSlug = "/diensten/website-laten-maken/";
70 |
71 | const breadcrumbSchema = {
72 | "@context": "https://schema.org",
73 | "@type": "BreadcrumbList",
74 | name: pageTitle,
75 | itemListElement: [
76 | {
77 | "@type": "ListItem",
78 | position: 1,
79 | name: "Diensten",
80 | item: siteUrl + "/diensten/",
81 | },
82 | {
83 | "@type": "ListItem",
84 | position: 2,
85 | name: pageTitle,
86 | item: siteUrl + pageSlug,
87 | },
88 | ],
89 | };
90 |
91 | const serviceSchema = {
92 | "@context": "https://schema.org",
93 | "@type": "Service",
94 | name: pageTitle,
95 | description: pageDescription,
96 | provider: {
97 | "@id": siteUrl + "/#organization",
98 | },
99 | serviceType: pageTitle,
100 | category: "Web Development",
101 | areaServed: {
102 | "@type": "Country",
103 | name: "Netherlands",
104 | },
105 | };
106 |
107 | return (
108 |
115 | );
116 | };
117 |
--------------------------------------------------------------------------------
/src/pages/diensten/onderhoud-updates-uitvoeren.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import { Link } from "gatsby";
4 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
5 |
6 | import useTranslation from "../../hooks/use-translation";
7 | import useSiteMetadata from "../../hooks/use-site-metadata";
8 |
9 | import Layout from "../../components/layout";
10 | import SEO from "../../components/seo";
11 |
12 | import * as servicesStyles from "../../styles/modules/pages/services.module.scss";
13 |
14 | // TODO: klaar voor TS'en..
15 |
16 | const MaintenancePage = () => {
17 | const { t, isHydrated } = useTranslation();
18 |
19 | if (!isHydrated) return null;
20 |
21 | return (
22 |
23 |
24 |
29 | {t("services.maintenance.intro")}
30 |
31 |
32 |
33 |
34 |
40 |
41 |
42 |
43 | {" "}
44 | {t("services.allServices")}
45 |
46 |
47 | {t("services.goToPrices")}{" "}
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | );
59 | };
60 |
61 | export default MaintenancePage;
62 |
63 | export const Head = () => {
64 | const { siteUrl } = useSiteMetadata();
65 |
66 | const pageTitle = "Onderhoud en/of updates uitvoeren";
67 | const pageDescription =
68 | "Regelmatig onderhoud en updates om de optimale prestaties en veiligheid van je website of webapplicatie te garanderen. Wij zorgen ervoor dat alles up-to-date blijft en probleemloos werkt.";
69 | const pageSlug = "/diensten/onderhoud-updates-uitvoeren/";
70 |
71 | const breadcrumbSchema = {
72 | "@context": "https://schema.org",
73 | "@type": "BreadcrumbList",
74 | name: pageTitle,
75 | itemListElement: [
76 | {
77 | "@type": "ListItem",
78 | position: 1,
79 | name: "Diensten",
80 | item: siteUrl + "/diensten/",
81 | },
82 | {
83 | "@type": "ListItem",
84 | position: 2,
85 | name: pageTitle,
86 | item: siteUrl + pageSlug,
87 | },
88 | ],
89 | };
90 |
91 | const serviceSchema = {
92 | "@context": "https://schema.org",
93 | "@type": "Service",
94 | name: pageTitle,
95 | description: pageDescription,
96 | provider: {
97 | "@id": siteUrl + "/#organization",
98 | },
99 | serviceType: pageTitle,
100 | category: "Web Development",
101 | areaServed: {
102 | "@type": "Country",
103 | name: "Netherlands",
104 | },
105 | };
106 |
107 | return (
108 |
115 | );
116 | };
117 |
--------------------------------------------------------------------------------
/src/pages/diensten/zoekmachineoptimalisatie.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import { Link } from "gatsby";
4 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
5 |
6 | import useTranslation from "../../hooks/use-translation";
7 | import useSiteMetadata from "../../hooks/use-site-metadata";
8 |
9 | import Layout from "../../components/layout";
10 | import SEO from "../../components/seo";
11 |
12 | import * as servicesStyles from "../../styles/modules/pages/services.module.scss";
13 |
14 | // TODO: klaar voor TS'en..
15 |
16 | const SeoPage = () => {
17 | const { t, isHydrated } = useTranslation();
18 |
19 | if (!isHydrated) return null;
20 |
21 | return (
22 |
23 |
24 |
29 | {t("services.seo.intro")}
30 |
31 |
32 |
33 |
34 |
40 |
41 |
42 |
43 | {" "}
44 | {t("services.allServices")}
45 |
46 |
47 | {t("services.goToPrices")}{" "}
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | );
59 | };
60 |
61 | export default SeoPage;
62 |
63 | export const Head = () => {
64 | const { siteUrl } = useSiteMetadata();
65 |
66 | const pageTitle = "Zoekmachineoptimalisatie (SEO)";
67 | const pageDescription =
68 | "Verbeter de vindbaarheid van je website in zoekmachines met gerichte SEO-strategieën. Wij optimaliseren je website om hogere posities te behalen in zoekresultaten, zodat je meer verkeer en klanten aantrekt.";
69 | const pageSlug = "/diensten/zoekmachineoptimalisatie/";
70 |
71 | const breadcrumbSchema = {
72 | "@context": "https://schema.org",
73 | "@type": "BreadcrumbList",
74 | name: pageTitle,
75 | itemListElement: [
76 | {
77 | "@type": "ListItem",
78 | position: 1,
79 | name: "Diensten",
80 | item: siteUrl + "/diensten/",
81 | },
82 | {
83 | "@type": "ListItem",
84 | position: 2,
85 | name: pageTitle,
86 | item: siteUrl + pageSlug,
87 | },
88 | ],
89 | };
90 |
91 | const serviceSchema = {
92 | "@context": "https://schema.org",
93 | "@type": "Service",
94 | name: pageTitle,
95 | description: pageDescription,
96 | provider: {
97 | "@id": siteUrl + "/#organization",
98 | },
99 | serviceType: pageTitle,
100 | category: "Digital Marketing",
101 | areaServed: {
102 | "@type": "Country",
103 | name: "Netherlands",
104 | },
105 | };
106 |
107 | return (
108 |
115 | );
116 | };
117 |
--------------------------------------------------------------------------------
/src/styles/modules/pages/faq.module.scss:
--------------------------------------------------------------------------------
1 | @use "../../customs" as *;
2 |
3 | .faq {
4 | $faq: &;
5 |
6 | &-overview {
7 | #{$faq}-container {
8 | display: flex;
9 | flex-direction: column;
10 | gap: rem-calc(48);
11 |
12 | @include container;
13 |
14 | > div {
15 | scroll-margin-top: rem-calc(120);
16 |
17 | #{$faq}-category {
18 | margin-bottom: rem-calc(24);
19 | padding-bottom: rem-calc(8);
20 | border-bottom: 2px solid rgba($lemon-color, 0.75);
21 |
22 | > h2 {
23 | @extend %as-h4;
24 | }
25 |
26 | &-items {
27 | display: flex;
28 | flex-direction: column;
29 | gap: rem-calc(16);
30 | }
31 | }
32 | }
33 |
34 | #{$faq}-item {
35 | #{$faq}-question {
36 | padding-bottom: rem-calc(12);
37 |
38 | > h3 {
39 | font-family: $poppins-font;
40 | font-weight: 600;
41 | letter-spacing: $medium-spacing;
42 | opacity: 0.75;
43 |
44 | @include smooth-trans;
45 | @include underline($color: $smoke-color);
46 |
47 | @include fluid-typing(18, 21);
48 | }
49 |
50 | &[aria-expanded="true"] {
51 | > h3 {
52 | opacity: 1;
53 | }
54 | }
55 |
56 | &:hover {
57 | > h3 {
58 | text-decoration-color: $link-color;
59 | opacity: 1;
60 | }
61 | }
62 | }
63 |
64 | #{$faq}-answer {
65 | max-height: 0;
66 | opacity: 0;
67 | overflow: hidden;
68 | transition: max-height 0.4s cubic-bezier(0.4, 0, 0.2, 1),
69 | opacity 0.4s;
70 | display: flex;
71 | gap: rem-calc(12);
72 |
73 | > svg {
74 | color: $secondary-color;
75 | padding-top: rem-calc(4);
76 |
77 | @include mobile {
78 | padding-top: rem-calc(5);
79 | }
80 | }
81 |
82 | > p {
83 | padding-bottom: rem-calc(12);
84 | max-width: 70ch;
85 | opacity: 0.95;
86 |
87 | @include fluid-typing(17, 19);
88 |
89 | a {
90 | @include text-link;
91 | }
92 | }
93 |
94 | &.open {
95 | max-height: fit-content;
96 | opacity: 1;
97 | transition: max-height 0.5s cubic-bezier(0.4, 0, 0.2, 1),
98 | opacity 0.5s;
99 | }
100 | }
101 | }
102 | }
103 |
104 | #{$faq}-navigation {
105 | display: flex;
106 | flex-wrap: wrap;
107 | gap: rem-calc(12);
108 |
109 | @include container($mb: rem-calc(48));
110 |
111 | > button {
112 | padding: rem-calc(8 16);
113 | border: 2px solid $snow-color;
114 | border-radius: $medium-radius;
115 | font-weight: 500;
116 |
117 | @include smooth-trans(all, 0.3s);
118 |
119 | @include mobile {
120 | padding: rem-calc(10 20);
121 | }
122 |
123 | &:hover {
124 | border-color: $link-color;
125 | background-color: $smoke-color;
126 | transform: translateY(-2px);
127 | }
128 | }
129 | }
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/src/pages/diensten/webshop-laten-maken.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import { Link } from "gatsby";
4 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
5 |
6 | import useTranslation from "../../hooks/use-translation";
7 | import useSiteMetadata from "../../hooks/use-site-metadata";
8 |
9 | import Layout from "../../components/layout";
10 | import SEO from "../../components/seo";
11 |
12 | import * as servicesStyles from "../../styles/modules/pages/services.module.scss";
13 |
14 | // TODO: klaar voor TS'en..
15 |
16 | const WebshopPage = () => {
17 | const { t, isHydrated } = useTranslation();
18 |
19 | if (!isHydrated) return null;
20 |
21 | return (
22 |
23 |
24 |
29 | {t("services.webshops.intro")}
30 |
31 |
32 |
33 |
34 |
40 |
41 |
42 |
43 | {" "}
44 | {t("services.allServices")}
45 |
46 |
47 | {t("services.goToPrices")}{" "}
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | );
59 | };
60 |
61 | export default WebshopPage;
62 |
63 | export const Head = () => {
64 | const { siteUrl } = useSiteMetadata();
65 |
66 | const pageTitle = "Webshop laten maken";
67 | const pageDescription =
68 | "Professioneel ontworpen webshops die volledig zijn afgestemd op jouw producten en doelgroep. Onze op maat gemaakte webshops bieden een gebruiksvriendelijke interface en optimale functionaliteit om jouw online verkoop te maximaliseren.";
69 | const pageSlug = "/diensten/webshop-laten-maken/";
70 |
71 | const breadcrumbSchema = {
72 | "@context": "https://schema.org",
73 | "@type": "BreadcrumbList",
74 | name: pageTitle,
75 | itemListElement: [
76 | {
77 | "@type": "ListItem",
78 | position: 1,
79 | name: "Diensten",
80 | item: siteUrl + "/diensten/",
81 | },
82 | {
83 | "@type": "ListItem",
84 | position: 2,
85 | name: pageTitle,
86 | item: siteUrl + pageSlug,
87 | },
88 | ],
89 | };
90 |
91 | const serviceSchema = {
92 | "@context": "https://schema.org",
93 | "@type": "Service",
94 | name: pageTitle,
95 | description: pageDescription,
96 | provider: {
97 | "@id": siteUrl + "/#organization",
98 | },
99 | serviceType: pageTitle,
100 | category: "Web Development",
101 | areaServed: {
102 | "@type": "Country",
103 | name: "Netherlands",
104 | },
105 | };
106 |
107 | return (
108 |
115 | );
116 | };
117 |
--------------------------------------------------------------------------------
/src/pages/diensten/webapplicatie-laten-maken.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import { Link } from "gatsby";
4 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
5 |
6 | import useTranslation from "../../hooks/use-translation";
7 | import useSiteMetadata from "../../hooks/use-site-metadata";
8 |
9 | import Layout from "../../components/layout";
10 | import SEO from "../../components/seo";
11 |
12 | import * as servicesStyles from "../../styles/modules/pages/services.module.scss";
13 |
14 | // TODO: klaar voor TS'en..
15 |
16 | const WebappPage = () => {
17 | const { t, isHydrated } = useTranslation();
18 |
19 | if (!isHydrated) return null;
20 |
21 | return (
22 |
23 |
24 |
29 | {t("services.webapps.intro")}
30 |
31 |
32 |
33 |
34 |
40 |
41 |
42 |
43 | {" "}
44 | {t("services.allServices")}
45 |
46 |
47 | {t("services.goToPrices")}{" "}
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | );
59 | };
60 |
61 | export default WebappPage;
62 |
63 | export const Head = () => {
64 | const { siteUrl } = useSiteMetadata();
65 |
66 | const pageTitle = "Webapplicatie laten maken";
67 | const pageDescription =
68 | "Professioneel ontworpen webapplicaties die volledig zijn afgestemd op jouw bedrijfsprocessen en gebruikersbehoeften. Onze op maat gemaakte webapplicaties bieden een gebruiksvriendelijke interface en optimale functionaliteit om jouw online succes te maximaliseren.";
69 | const pageSlug = "/diensten/webapplicatie-laten-maken/";
70 |
71 | const breadcrumbSchema = {
72 | "@context": "https://schema.org",
73 | "@type": "BreadcrumbList",
74 | name: pageTitle,
75 | itemListElement: [
76 | {
77 | "@type": "ListItem",
78 | position: 1,
79 | name: "Diensten",
80 | item: siteUrl + "/diensten/",
81 | },
82 | {
83 | "@type": "ListItem",
84 | position: 2,
85 | name: pageTitle,
86 | item: siteUrl + pageSlug,
87 | },
88 | ],
89 | };
90 |
91 | const serviceSchema = {
92 | "@context": "https://schema.org",
93 | "@type": "Service",
94 | name: pageTitle,
95 | description: pageDescription,
96 | provider: {
97 | "@id": siteUrl + "/#organization",
98 | },
99 | serviceType: pageTitle,
100 | category: "Web Development",
101 | areaServed: {
102 | "@type": "Country",
103 | name: "Netherlands",
104 | },
105 | };
106 |
107 | return (
108 |
115 | );
116 | };
117 |
--------------------------------------------------------------------------------
/src/pages/diensten/optimalisaties-laten-uitvoeren.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import { Link } from "gatsby";
4 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
5 |
6 | import useTranslation from "../../hooks/use-translation";
7 | import useSiteMetadata from "../../hooks/use-site-metadata";
8 |
9 | import Layout from "../../components/layout";
10 | import SEO from "../../components/seo";
11 |
12 | import * as servicesStyles from "../../styles/modules/pages/services.module.scss";
13 |
14 | // TODO: klaar voor TS'en..
15 |
16 | const OptimizationPage = () => {
17 | const { t, isHydrated } = useTranslation();
18 |
19 | if (!isHydrated) return null;
20 |
21 | return (
22 |
23 |
24 |
29 | {t("services.optimizations.intro")}
30 |
31 |
32 |
33 |
34 |
40 |
41 |
42 |
43 | {" "}
44 | {t("services.allServices")}
45 |
46 |
47 | {t("services.goToPrices")}{" "}
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | );
59 | };
60 |
61 | export default OptimizationPage;
62 |
63 | export const Head = () => {
64 | const { siteUrl } = useSiteMetadata();
65 |
66 | const pageTitle = "Optimalisaties laten uitvoeren";
67 | const pageDescription =
68 | "Professioneel ontworpen optimalisaties die volledig zijn afgestemd op jouw bedrijfsprocessen en gebruikersbehoeften. Onze op maat gemaakte optimalisaties bieden een gebruiksvriendelijke interface en optimale functionaliteit om jouw online succes te maximaliseren.";
69 | const pageSlug = "/diensten/optimalisaties-laten-uitvoeren/";
70 |
71 | const breadcrumbSchema = {
72 | "@context": "https://schema.org",
73 | "@type": "BreadcrumbList",
74 | name: pageTitle,
75 | itemListElement: [
76 | {
77 | "@type": "ListItem",
78 | position: 1,
79 | name: "Diensten",
80 | item: siteUrl + "/diensten/",
81 | },
82 | {
83 | "@type": "ListItem",
84 | position: 2,
85 | name: pageTitle,
86 | item: siteUrl + pageSlug,
87 | },
88 | ],
89 | };
90 |
91 | const serviceSchema = {
92 | "@context": "https://schema.org",
93 | "@type": "Service",
94 | name: pageTitle,
95 | description: pageDescription,
96 | provider: {
97 | "@id": siteUrl + "/#organization",
98 | },
99 | serviceType: pageTitle,
100 | category: "Web Development",
101 | areaServed: {
102 | "@type": "Country",
103 | name: "Netherlands",
104 | },
105 | };
106 |
107 | return (
108 |
115 | );
116 | };
117 |
--------------------------------------------------------------------------------
/src/styles/modules/ui/prices.module.scss:
--------------------------------------------------------------------------------
1 | @use "../../customs" as *;
2 |
3 | .price {
4 | $price: &;
5 |
6 | &-container {
7 | display: grid;
8 | gap: rem-calc(24);
9 | grid-template-columns: 1fr;
10 |
11 | @include container;
12 |
13 | @include mobile {
14 | grid-template-columns: repeat(2, 1fr);
15 | }
16 |
17 | @include laptop {
18 | grid-template-columns: repeat(4, 1fr);
19 | }
20 |
21 | #{$price}-table {
22 | display: flex;
23 | flex-direction: column;
24 | border: 2px $primary-color solid;
25 | border-radius: $high-radius;
26 | background-color: $white-color;
27 | overflow: hidden;
28 | flex: 1;
29 |
30 | @include smooth-trans;
31 |
32 | > h3 {
33 | text-align: center;
34 | text-decoration: underline;
35 | text-underline-offset: rem-calc(5);
36 | text-decoration-thickness: 2px;
37 | text-decoration-color: $ivory-color;
38 | padding: rem-calc(8 0);
39 | background: linear-gradient(
40 | to bottom,
41 | $secondary-color,
42 | $secondary-color
43 | );
44 |
45 | @include fluid-typing(28, 28);
46 | }
47 |
48 | > div {
49 | background-color: rgba($primary-color, 1);
50 | color: $white-color;
51 | padding: rem-calc(16 0 24);
52 |
53 | #{$price}-main {
54 | text-align: center;
55 | font-weight: 600;
56 | margin-bottom: rem-calc(24);
57 |
58 | > data {
59 | @include fluid-typing(24, 26);
60 | }
61 |
62 | > span {
63 | text-decoration: line-through;
64 |
65 | @include fluid-typing(13, 14);
66 | }
67 |
68 | > sup {
69 | font-weight: 500;
70 | display: block;
71 | top: 0.5em;
72 |
73 | @include fluid-typing(12, 13);
74 | }
75 | }
76 |
77 | #{$price}-action {
78 | text-align: center;
79 | font-weight: bolder;
80 | color: $link-color;
81 | background-color: $white-color;
82 | width: fit-content;
83 | margin: 0 auto rem-calc(24);
84 | padding: rem-calc(2 8);
85 | border-radius: $low-radius;
86 |
87 | @include fluid-typing(12, 13);
88 | }
89 |
90 | #{$price}-contains {
91 | text-align: center;
92 | font-weight: bold;
93 | text-decoration: underline;
94 | text-decoration-color: $ivory-color;
95 |
96 | @include fluid-typing(12, 13);
97 | }
98 | }
99 |
100 | > ul {
101 | margin-bottom: rem-calc(32);
102 |
103 | > li {
104 | text-align: center;
105 | border-bottom: 1px $lightgrey-color solid;
106 | padding: rem-calc(14 12);
107 |
108 | @include fluid-typing(15, 16);
109 | }
110 |
111 | #{$price}-feat {
112 | font-style: italic;
113 | font-weight: 600;
114 | color: $grey-color;
115 |
116 | @include fluid-typing(13, 14);
117 | }
118 |
119 | #{$price}-bold {
120 | font-weight: 700;
121 | font-style: oblique;
122 | background-color: $smoke-color;
123 | }
124 | }
125 |
126 | > button {
127 | margin: auto auto rem-calc(16);
128 |
129 | @include primary-button(1, 90%);
130 | }
131 |
132 | &:hover {
133 | transform: scale(1.0125);
134 | }
135 | }
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/functions/sendmail/sendmail.js:
--------------------------------------------------------------------------------
1 | const sgMail = require("@sendgrid/mail");
2 | const axios = require("axios");
3 |
4 | sgMail.setApiKey(process.env.SENDGRID_API_KEY);
5 |
6 | exports.handler = async (event) => {
7 | if (event.httpMethod !== "POST") {
8 | return { statusCode: 405, body: "Method Not Allowed" };
9 | }
10 |
11 | const { recaptchaToken, formId, ...formData } = JSON.parse(event.body);
12 |
13 | try {
14 | const verifyResponse = await axios.post(
15 | "https://www.google.com/recaptcha/api/siteverify",
16 | null,
17 | {
18 | params: {
19 | secret: process.env.RECAPTCHA_SECRET_KEY,
20 | response: recaptchaToken,
21 | },
22 | }
23 | );
24 |
25 | const { success, score, action } = verifyResponse.data;
26 |
27 | const expectedActions = {
28 | leadForm: "leadFormSubmit",
29 | callForm: "callFormSubmit",
30 | smartForm: "smartFormSubmit",
31 | };
32 |
33 | if (!success || score < 0.5 || action !== expectedActions[formId]) {
34 | console.error("❌ reCAPTCHA failed:", {
35 | success,
36 | score,
37 | action,
38 | expected: expectedActions[formId],
39 | });
40 | return {
41 | statusCode: 400,
42 | body: JSON.stringify({
43 | error: "reCAPTCHA verification failed",
44 | debug: { success, score, action },
45 | }),
46 | };
47 | }
48 |
49 | let dynamic_template_data = {};
50 |
51 | switch (formId) {
52 | case "leadForm":
53 | dynamic_template_data = {
54 | mnfxForm: formId || "n.v.t.",
55 | mnfxName: formData.name || "n.v.t.",
56 | mnfxCompany: formData.company || "n.v.t.",
57 | mnfxEmail: formData.email || "n.v.t.",
58 | mnfxPhone: formData.tel || "n.v.t.",
59 | mnfxSubject: formData.subject || "n.v.t.",
60 | mnfxMessage: formData.message || "n.v.t.",
61 | };
62 | break;
63 | case "callForm":
64 | dynamic_template_data = {
65 | mnfxForm: formId || "n.v.t.",
66 | mnfxName: formData.name || "n.v.t.",
67 | mnfxPhone: formData.tel || "n.v.t.",
68 | mnfxTime: formData.time || "n.v.t.",
69 | mnfxMessage: formData.message || "n.v.t.",
70 | };
71 | break;
72 | case "smartForm":
73 | dynamic_template_data = {
74 | mnfxForm: formId || "n.v.t.",
75 | mnfxName: formData.name || "n.v.t.",
76 | mnfxNeed: formData.need || "n.v.t.",
77 | mnfxHasWebsite: formData.hasWebsite || "n.v.t.",
78 | mnfxWebsiteUrl: formData.websiteUrl || "n.v.t.",
79 | mnfxSelfManage: formData.selfManage || "n.v.t.",
80 | mnfxBudget: formData.budget || "n.v.t.",
81 | mnfxEmail: formData.email || "n.v.t.",
82 | mnfxPhone: formData.tel || "n.v.t.",
83 | mnfxMessage: formData.message || "n.v.t.",
84 | };
85 | break;
86 | default:
87 | dynamic_template_data = { ...formData };
88 | }
89 |
90 | const msg = {
91 | from: {
92 | email: process.env.SENDGRID_AUTHORIZED_EMAIL,
93 | name: "Menefex WMB - [website] ✨",
94 | },
95 | templateId: "d-8eebc10097fd430787de9cd0f3db702b",
96 | personalizations: [
97 | {
98 | to: "info@menefex.nl",
99 | dynamic_template_data,
100 | },
101 | ],
102 | };
103 |
104 | await sgMail.send(msg);
105 | return { statusCode: 202, body: "Bericht verstuurd" };
106 | } catch (error) {
107 | console.error("❌ Error:", error.message);
108 | const statusCode = typeof error.code === "number" ? error.code : 500;
109 | return { statusCode, body: error.message };
110 | }
111 | };
112 |
--------------------------------------------------------------------------------
/src/pages/index.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import useSiteMetadata from "../hooks/use-site-metadata";
4 |
5 | import Layout from "../components/layout";
6 | import SEO from "../components/seo";
7 |
8 | import Hero from "../components/layout/hero";
9 | import Biography from "../components/ui/biography";
10 | import Actual from "../components/ui/actual";
11 | import Services from "../components/ui/services";
12 | import Projects from "../components/ui/projects";
13 | import Usp from "../components/ui/usp";
14 | import Faq from "../components/ui/faq";
15 | import SmartForm from "../components/forms/smartForm";
16 |
17 | import * as indexStyles from "../styles/modules/pages/index.module.scss";
18 |
19 | // TODO: klaar voor TS'en..
20 |
21 | const IndexPage = () => {
22 | return (
23 |
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 | export default IndexPage;
58 |
59 | export const Head = () => {
60 | const { title, siteUrl, image, telephone, description, email, handle } =
61 | useSiteMetadata();
62 |
63 | const localBusinessSchema = {
64 | "@context": "https://schema.org",
65 | "@type": "LocalBusiness",
66 | "@id": siteUrl + "/#localbusiness",
67 | name: title,
68 | parentOrganization: {
69 | "@id": siteUrl + "/#organization",
70 | },
71 | image: siteUrl + image,
72 | logo: {
73 | "@id": siteUrl + "/#logo",
74 | },
75 | description: description,
76 | url: siteUrl,
77 | telephone: telephone,
78 | email: email,
79 | hasMap: `https://g.page/${handle}?share`,
80 | areaServed: {
81 | "@type": "GeoCircle",
82 | geoMidpoint: {
83 | "@id": siteUrl + "/#geo",
84 | },
85 | geoRadius: "1000",
86 | },
87 | priceRange: "$$",
88 | address: {
89 | "@type": "PostalAddress",
90 | streetAddress: "Kelbergen 192",
91 | postalCode: "1104LJ",
92 | addressLocality: "Amsterdam",
93 | addressRegion: "Noord-Holland",
94 | addressCountry: "NL",
95 | },
96 | geo: {
97 | "@type": "GeoCoordinates",
98 | "@id": siteUrl + "/#geo",
99 | latitude: "52.31049600748774",
100 | longitude: "4.973736770446289",
101 | },
102 | openingHoursSpecification: {
103 | "@type": "OpeningHoursSpecification",
104 | dayOfWeek: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
105 | opens: "09:00:00",
106 | closes: "19:00:00",
107 | },
108 | sameAs: [
109 | `https://www.facebook.com/${handle}`,
110 | `https://www.x.com/${handle}`,
111 | `https://www.instagram.com/${handle}/`,
112 | `https://www.linkedin.com/company/${handle}/`,
113 | `https://github.com/mikeyfe6`,
114 | `https://www.patreon.com/${handle}`,
115 | `https://feeds.feedburner.com/${handle}`,
116 | `https://wa.me/${telephone}`,
117 | `https://open.spotify.com/playlist/08UGoWTjvpuooABCWyPx0m?si=5a3ca09f8cba4300`,
118 | ],
119 | };
120 |
121 | return (
122 | <>
123 |
129 | >
130 | );
131 | };
132 |
--------------------------------------------------------------------------------
/src/components/ui/usp.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
4 |
5 | import useTranslation from "../../hooks/use-translation";
6 |
7 | import * as uspStyles from "../../styles/modules/ui/usp.module.scss";
8 |
9 | const Usp = () => {
10 | const { t, isHydrated } = useTranslation();
11 |
12 | if (!isHydrated) return null;
13 |
14 | return (
15 |
16 |
17 |
18 | -
19 |
20 |
21 |
25 |
30 |
34 |
35 |
36 | -
37 |
38 |
39 |
43 |
48 |
52 |
53 |
54 |
55 | -
56 |
57 |
58 |
62 |
67 |
71 |
72 |
73 | -
74 |
75 |
76 |
80 |
85 |
89 |
90 |
91 | -
92 |
93 |
94 |
98 |
103 |
107 |
108 |
109 |
110 |
111 |
112 | );
113 | };
114 |
115 | export default Usp;
116 |
--------------------------------------------------------------------------------
/src/components/navbar/mobileMenu.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import { Link } from "gatsby";
4 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
5 |
6 | import useTranslation from "../../hooks/use-translation";
7 | import useSiteMetadata from "../../hooks/use-site-metadata";
8 |
9 | import * as mobileMenuStyles from "../../styles/modules/layout/mobileMenu.module.scss";
10 |
11 | const MobileMenu = ({ show }) => {
12 | const { t, isHydrated } = useTranslation();
13 | const { telephone } = useSiteMetadata();
14 |
15 | let drawerClasses = mobileMenuStyles.mobileMenu;
16 | if (show) {
17 | drawerClasses = `${mobileMenuStyles.mobileMenu} ${mobileMenuStyles.open}`;
18 | }
19 |
20 | const checkIfPartiallyActive = ({ isPartiallyCurrent, location }) => {
21 | return location.pathname.includes("/blog") ||
22 | location.pathname.includes("/topics")
23 | ? { className: mobileMenuStyles.activePage }
24 | : isPartiallyCurrent
25 | ? { className: mobileMenuStyles.activePage }
26 | : null;
27 | };
28 |
29 | if (!isHydrated) return null;
30 |
31 | return (
32 |
120 | );
121 | };
122 |
123 | export default MobileMenu;
124 |
--------------------------------------------------------------------------------
/src/styles/modules/pages/portfolio.module.scss:
--------------------------------------------------------------------------------
1 | @use "../../customs" as *;
2 |
3 | .portfolio {
4 | $portfolio: &;
5 |
6 | &-container {
7 | display: grid;
8 | grid-template-columns: repeat(1, 1fr);
9 | gap: rem-calc(48 32);
10 |
11 | @include container;
12 |
13 | @include tablet {
14 | grid-template-columns: repeat(2, 1fr);
15 | }
16 |
17 | > div {
18 | display: flex;
19 | flex-direction: column;
20 | scroll-margin-top: rem-calc(100);
21 |
22 | @include phablet {
23 | scroll-margin-top: rem-calc(130);
24 | }
25 |
26 | &:has(img) {
27 | img {
28 | aspect-ratio: 16/9;
29 | }
30 |
31 | #{$portfolio}-btns {
32 | margin-top: auto;
33 | }
34 | }
35 |
36 | &:has(img[alt*="404"]) {
37 | #{$portfolio}-img {
38 | pointer-events: none;
39 | }
40 | }
41 |
42 | #{$portfolio}-img {
43 | display: block;
44 | border: 2px $snow-color solid;
45 | border-radius: $medium-radius;
46 | margin-bottom: rem-calc(24);
47 | overflow: hidden;
48 | z-index: 1;
49 |
50 | > [data-gatsby-image-wrapper] {
51 | width: 100%;
52 | height: 100%;
53 | display: block;
54 | }
55 |
56 | @include smooth-trans;
57 |
58 | @include phablet {
59 | margin-bottom: rem-calc(32);
60 | }
61 |
62 | &:hover {
63 | opacity: 0.7;
64 | border-color: $link-color;
65 | }
66 | }
67 |
68 | #{$portfolio}-info {
69 | padding: rem-calc(8 24);
70 | border-left: 2px solid $smoke-color;
71 | border-right: 2px solid $smoke-color;
72 | min-height: rem-calc(250);
73 | display: flex;
74 | flex-direction: column;
75 | margin-bottom: rem-calc(24);
76 |
77 | @include phablet {
78 | margin-bottom: rem-calc(32);
79 | }
80 |
81 | > h3 {
82 | display: inline;
83 |
84 | @include poppins-title;
85 |
86 | @include fluid-typing(20, 21);
87 |
88 | > span {
89 | margin-right: rem-calc(16);
90 | color: $secondary-color;
91 | font-weight: 600;
92 | display: inline;
93 |
94 | @extend %as-medium;
95 | }
96 | }
97 |
98 | > p {
99 | margin: rem-calc(16 0 28);
100 |
101 | @extend %as-medium;
102 | }
103 |
104 | .tools {
105 | display: flex;
106 | align-items: center;
107 | margin-top: auto;
108 |
109 | > span {
110 | margin-right: rem-calc(16);
111 | color: $secondary-color;
112 | font-weight: 600;
113 | display: inline;
114 | }
115 |
116 | > ul {
117 | display: flex;
118 | flex-wrap: wrap;
119 | gap: rem-calc(4);
120 |
121 | > li {
122 | background: linear-gradient(
123 | 145deg,
124 | $primary-color,
125 | $dark-color
126 | );
127 | border-radius: $low-radius;
128 | padding: rem-calc(2 12);
129 | width: fit-content;
130 | color: $smoke-color;
131 | white-space: nowrap;
132 |
133 | @include fluid-typing(10, 11);
134 | }
135 | }
136 | }
137 | }
138 |
139 | #{$portfolio}-btns {
140 | display: flex;
141 | gap: rem-calc(16);
142 |
143 | .btn {
144 | &-dark,
145 | &-light {
146 | > svg {
147 | font-size: rem-calc(18);
148 | }
149 | }
150 |
151 | &-dark {
152 | @include dark-button;
153 | }
154 |
155 | &-light {
156 | @include light-button;
157 | }
158 | }
159 | }
160 | }
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/src/components/ui/faq.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 |
3 | import { Link } from "gatsby";
4 |
5 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
6 |
7 | import useTranslation from "../../hooks/use-translation";
8 |
9 | import { StaticImage } from "gatsby-plugin-image";
10 |
11 | import * as faqStyles from "../../styles/modules/ui/faq.module.scss";
12 |
13 | const Faq = () => {
14 | const { t, isHydrated } = useTranslation();
15 | const [openIndex, setOpenIndex] = useState(null);
16 |
17 | const faqData = [
18 | {
19 | question: t("faq.itemOne.question"),
20 | answer: t("faq.itemOne.answer"),
21 | },
22 | {
23 | question: t("faq.itemTwo.question"),
24 | answer: t("faq.itemTwo.answer"),
25 | },
26 | {
27 | question: t("faq.itemThree.question"),
28 | answer: t("faq.itemThree.answer"),
29 | link: "/prijzen/",
30 | },
31 | {
32 | question: t("faq.itemFour.question"),
33 | answer: t("faq.itemFour.answer"),
34 | },
35 | {
36 | question: t("faq.itemFive.question"),
37 | answer: t("faq.itemFive.answer"),
38 | },
39 | {
40 | question: t("faq.itemSix.question"),
41 | answer: t("faq.itemSix.answer"),
42 | },
43 | {
44 | question: t("faq.itemSeven.question"),
45 | answer: t("faq.itemSeven.answer"),
46 | },
47 | {
48 | question: t("faq.itemEight.question"),
49 | answer: t("faq.itemEight.answer"),
50 | },
51 | {
52 | question: t("faq.itemNine.question"),
53 | answer: t("faq.itemNine.answer"),
54 | },
55 | {
56 | question: t("faq.itemTen.question"),
57 | answer: t("faq.itemTen.answer"),
58 | },
59 | ];
60 |
61 | if (!isHydrated) return null;
62 |
63 | return (
64 |
65 |
66 |
67 |
68 |
75 |
76 |
77 |
78 | {t("faq.title")}
79 | {t("faq.seeAll")}
80 |
81 | {faqData.map((item, idx) => (
82 |
83 |
96 |
101 |
105 |
106 | {item.answer}{" "}
107 | {item.link && (
108 | <>
109 | {t("faq.priceText")}{" "}
110 |
111 | {t("faq.priceLink")}
112 |
113 | .
114 | >
115 | )}
116 |
117 |
118 |
119 | ))}
120 |
121 |
122 |
123 |
124 | );
125 | };
126 |
127 | export default Faq;
128 |
--------------------------------------------------------------------------------
/src/components/layout.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useRef, useEffect } from "react";
2 |
3 | import CookieConsent from "react-cookie-consent";
4 |
5 | import { library } from "@fortawesome/fontawesome-svg-core";
6 | import {
7 | faWhatsapp,
8 | faSpotify,
9 | faFacebookF,
10 | faInstagram,
11 | faXTwitter,
12 | faLinkedin,
13 | faGithub,
14 | // faPatreon,
15 | // faSkype,
16 | faSearchengin,
17 | } from "@fortawesome/free-brands-svg-icons";
18 | import {
19 | faRotateLeft,
20 | faArrowRightLong,
21 | faRightLong,
22 | faPhone,
23 | faEnvelope,
24 | faRss,
25 | faChevronLeft,
26 | faChevronRight,
27 | faGlobe,
28 | faMobile,
29 | faShoppingCart,
30 | faWrench,
31 | faRocket,
32 | fa1,
33 | fa2,
34 | fa3,
35 | fa4,
36 | fa5,
37 | faQuoteLeft,
38 | faQuoteRight,
39 | faCalendarDays,
40 | faFeatherPointed,
41 | faAnglesRight,
42 | faAnglesLeft,
43 | faEye,
44 | faEnvelopeCircleCheck,
45 | faCircleQuestion,
46 | faXmark,
47 | faCaretRight,
48 | } from "@fortawesome/free-solid-svg-icons";
49 | import { faPenToSquare } from "@fortawesome/free-regular-svg-icons";
50 |
51 | library.add(
52 | faPenToSquare,
53 | faRotateLeft,
54 | faArrowRightLong,
55 | faWhatsapp,
56 | faSpotify,
57 | faRightLong,
58 | faPhone,
59 | faEnvelope,
60 | faFacebookF,
61 | faInstagram,
62 | faXTwitter,
63 | faLinkedin,
64 | faGithub,
65 | faRss,
66 | // faPatreon,
67 | // faSkype,
68 | faChevronLeft,
69 | faChevronRight,
70 | faGlobe,
71 | faMobile,
72 | faShoppingCart,
73 | faSearchengin,
74 | faWrench,
75 | faRocket,
76 | fa1,
77 | fa2,
78 | fa3,
79 | fa4,
80 | fa5,
81 | faQuoteLeft,
82 | faQuoteRight,
83 | faCalendarDays,
84 | faFeatherPointed,
85 | faAnglesRight,
86 | faAnglesLeft,
87 | faEye,
88 | faEnvelopeCircleCheck,
89 | faCircleQuestion,
90 | faXmark,
91 | faCaretRight
92 | );
93 |
94 | import useTranslation from "../hooks/use-translation";
95 |
96 | import Banner from "./layout/banner";
97 | import DesktopMenu from "./navbar/desktopMenu";
98 | import MobileMenu from "./navbar/mobileMenu";
99 | import MenuOverlay from "./navbar/menuOverlay";
100 | import Footer from "./layout/footer";
101 |
102 | import ResponsiveTag from "./helpers/responsiveTag";
103 |
104 | import "../styles/layout.scss";
105 |
106 | // TODO: klaar voor TS'en..
107 |
108 | const Layout = ({ children }) => {
109 | const { t, isHydrated } = useTranslation();
110 | const [sideDrawerOpen, setSideDrawerOpen] = useState(false);
111 |
112 | const bannerRef = useRef(null);
113 |
114 | const drawerToggleClickHandler = () => {
115 | setSideDrawerOpen((prevState) => !prevState);
116 | };
117 |
118 | const menuOverlayClickHandler = () => {
119 | setSideDrawerOpen(false);
120 | };
121 |
122 | let backdrop;
123 |
124 | if (sideDrawerOpen) {
125 | backdrop = ;
126 | }
127 |
128 | if (!isHydrated) return null;
129 |
130 | return (
131 | <>
132 |
133 |
134 |
141 |
142 |
143 |
144 | {children}
145 |
146 |
147 |
148 |
149 | {process.env.NODE_ENV === "production" && (
150 |
168 | Cookies
169 |
174 |
175 | )}
176 |
177 |
178 | {backdrop}
179 |
180 | {process.env.NODE_ENV === "development" && }
181 | >
182 | );
183 | };
184 |
185 | export default Layout;
186 |
--------------------------------------------------------------------------------
/src/pages/topics.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import { Link, graphql, useStaticQuery } from "gatsby";
4 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
5 |
6 | import useTranslation from "../hooks/use-translation";
7 | import useSiteMetadata from "../hooks/use-site-metadata";
8 |
9 | import Layout from "../components/layout";
10 | import SEO from "../components/seo";
11 |
12 | import * as topicStyles from "../styles/modules/pages/topics.module.scss";
13 |
14 | // TODO: klaar voor TS'en..
15 |
16 | const TopicPage = () => {
17 | const { t, i18n, isHydrated } = useTranslation();
18 | const currentLanguage = i18n.language;
19 |
20 | const data = useStaticQuery(graphql`
21 | query TopicQuery {
22 | nlContent: allContentfulTopic(
23 | sort: { name: ASC }
24 | filter: { node_locale: { eq: "nl" } }
25 | ) {
26 | edges {
27 | node {
28 | contentful_id
29 | name
30 | slug
31 | bdcolor
32 | description {
33 | description
34 | }
35 | }
36 | }
37 | }
38 | enContent: allContentfulTopic(
39 | sort: { name: ASC }
40 | filter: { node_locale: { eq: "en" } }
41 | ) {
42 | edges {
43 | node {
44 | contentful_id
45 | name
46 | slug
47 | bdcolor
48 | description {
49 | description
50 | }
51 | }
52 | }
53 | }
54 | }
55 | `);
56 |
57 | const currentContent =
58 | currentLanguage === "nl" ? data.nlContent.edges : data.enContent.edges;
59 |
60 | if (!currentContent) {
61 | return Geen content beschikbaar / No content available.
;
62 | }
63 |
64 | if (!isHydrated) return null;
65 |
66 | return (
67 |
68 |
69 | Topics
70 | {t("topics.intro")}
71 |
72 |
73 |
74 |
75 |
103 |
104 |
105 | 'Blog'{" "}
106 |
107 |
108 |
109 |
110 |
111 | );
112 | };
113 |
114 | export default TopicPage;
115 |
116 | export const Head = () => {
117 | const { siteUrl } = useSiteMetadata();
118 |
119 | const pageTitle = "Topics";
120 | const pageSlug = "/topics/";
121 |
122 | const breadcrumbSchema = {
123 | "@context": "https://schema.org",
124 | "@type": "BreadcrumbList",
125 | name: pageTitle,
126 | itemListElement: [
127 | {
128 | "@type": "ListItem",
129 | position: 1,
130 | name: pageTitle,
131 | item: siteUrl + pageSlug,
132 | },
133 | ],
134 | };
135 |
136 | return (
137 |
144 | );
145 | };
146 |
--------------------------------------------------------------------------------
/src/styles/modules/ui/info.module.scss:
--------------------------------------------------------------------------------
1 | @use "sass:color";
2 |
3 | @use "../../customs" as *;
4 |
5 | .info {
6 | $info: &;
7 |
8 | background: linear-gradient(145deg, $primary-color, $dark-color);
9 | outline: 2px solid $primary-color;
10 | border-radius: $medium-radius;
11 | color: $white-color;
12 |
13 | @include box-padding("spaced");
14 |
15 | &-location {
16 | margin-bottom: rem-calc(24);
17 | }
18 |
19 | &-availability {
20 | margin-bottom: rem-calc(24);
21 | }
22 |
23 | &-times {
24 | margin-bottom: rem-calc(36);
25 |
26 | > li > span {
27 | color: $secondary-color;
28 | min-width: rem-calc(140);
29 | display: inline-block;
30 | }
31 | }
32 |
33 | &-details {
34 | margin-bottom: rem-calc(48);
35 |
36 | @extend %as-small;
37 | }
38 |
39 | &-communication {
40 | display: flex;
41 | justify-content: center;
42 | gap: rem-calc(32);
43 | flex-wrap: wrap;
44 | margin-bottom: rem-calc(48);
45 |
46 | #{$info}-tel,
47 | #{$info}-mail,
48 | #{$info}-whapp {
49 | display: flex;
50 | justify-content: center;
51 | align-items: center;
52 | gap: rem-calc(8);
53 | padding: rem-calc(8 16);
54 | border-radius: $low-radius;
55 | font-weight: 500;
56 | border: 3px solid rgba($white-color, 0.25);
57 | color: $white-color;
58 | width: 100%;
59 |
60 | @include mobile {
61 | width: fit-content;
62 | }
63 |
64 | @include phablet {
65 | padding: rem-calc(10 24);
66 | }
67 |
68 | > span {
69 | padding-right: rem-calc(4);
70 |
71 | @include fluid-typing(16, 18);
72 | }
73 | }
74 |
75 | #{$info}-tel,
76 | #{$info}-mail {
77 | > svg {
78 | font-size: rem-calc(18);
79 | }
80 | }
81 |
82 | #{$info}-whapp {
83 | > svg {
84 | font-size: rem-calc(24);
85 | }
86 | }
87 |
88 | #{$info}-tel {
89 | background-color: color.adjust($tel-color, $lightness: -2.5%);
90 |
91 | &:hover {
92 | background-color: color.adjust($tel-color, $lightness: -7.5%);
93 | }
94 | }
95 |
96 | #{$info}-mail {
97 | background-color: color.adjust($mail-color, $lightness: -2.5%);
98 |
99 | &:hover {
100 | background-color: color.adjust($mail-color, $lightness: -10%);
101 | }
102 | }
103 |
104 | #{$info}-whapp {
105 | background-color: $smoke-color;
106 | color: color.adjust($whapp-color, $lightness: -2.5%);
107 |
108 | &:hover {
109 | background-color: color.adjust($smoke-color, $lightness: -10%);
110 | }
111 | }
112 | }
113 |
114 | &-socials {
115 | display: flex;
116 | justify-content: center;
117 | align-items: center;
118 | flex-direction: column;
119 | margin-bottom: rem-calc(16);
120 |
121 | > p {
122 | text-align: center;
123 | margin-bottom: rem-calc(8);
124 | }
125 |
126 | > ul {
127 | display: flex;
128 | gap: rem-calc(12);
129 | justify-content: center;
130 | flex-wrap: wrap;
131 | margin-top: rem-calc(20);
132 |
133 | li {
134 | a {
135 | display: flex;
136 | align-items: center;
137 | justify-content: center;
138 | background-color: transparent;
139 | border-radius: $medium-radius;
140 | aspect-ratio: 1 / 1;
141 | padding: rem-calc(2);
142 |
143 | > svg {
144 | font-size: rem-calc(26);
145 |
146 | @include smooth-trans;
147 | }
148 |
149 | &:hover {
150 | background-color: $white-color;
151 | transform: scale(1.125);
152 | }
153 |
154 | &.fb:hover svg {
155 | color: #4064ad;
156 | }
157 |
158 | &.ig:hover svg {
159 | color: #dd2a7b;
160 | }
161 |
162 | &.tw:hover svg {
163 | color: #55acee;
164 | }
165 |
166 | &.li:hover svg {
167 | color: #0177b5;
168 | }
169 |
170 | &.gh:hover svg {
171 | color: #24292e;
172 | }
173 |
174 | &.rss:hover svg {
175 | color: #fa9b3b;
176 | }
177 |
178 | &.patr:hover svg {
179 | color: #f26451;
180 | }
181 |
182 | &.sky:hover svg {
183 | color: #05aae8;
184 | }
185 |
186 | &.sp:hover svg {
187 | color: #1db954;
188 | }
189 | }
190 | }
191 | }
192 | }
193 | }
194 |
--------------------------------------------------------------------------------
/src/styles/_typography.scss:
--------------------------------------------------------------------------------
1 | @use "./customs/" as *;
2 |
3 | @font-face {
4 | font-family: Poppins;
5 | src: url("../fonts/Poppins-Thin.woff2") format("woff2");
6 | font-weight: 100; /* Thin */
7 | font-style: normal;
8 | font-display: swap;
9 | }
10 |
11 | @font-face {
12 | font-family: Poppins;
13 | src: url("../fonts/Poppins-ThinItalic.woff2") format("woff2");
14 | font-weight: 100; /* Thin */
15 | font-style: italic;
16 | font-display: swap;
17 | }
18 |
19 | @font-face {
20 | font-family: Poppins;
21 | src: url("../fonts/Poppins-ExtraLight.woff2") format("woff2");
22 | font-weight: 200; /* ExtraLight */
23 | font-style: normal;
24 | font-display: swap;
25 | }
26 |
27 | @font-face {
28 | font-family: Poppins;
29 | src: url("../fonts/Poppins-ExtraLightItalic.woff2") format("woff2");
30 | font-weight: 200; /* ExtraLight */
31 | font-style: italic;
32 | font-display: swap;
33 | }
34 |
35 | @font-face {
36 | font-family: Poppins;
37 | src: url("../fonts/Poppins-Light.woff2") format("woff2");
38 | font-weight: 300; /* Light */
39 | font-style: normal;
40 | font-display: swap;
41 | }
42 |
43 | @font-face {
44 | font-family: Poppins;
45 | src: url("../fonts/Poppins-LightItalic.woff2") format("woff2");
46 | font-weight: 300; /* Light */
47 | font-style: italic;
48 | font-display: swap;
49 | }
50 |
51 | @font-face {
52 | font-family: Poppins;
53 | src: url("../fonts/Poppins-Regular.woff2") format("woff2");
54 | font-weight: 400; /* Regular */
55 | font-style: normal;
56 | font-display: swap;
57 | }
58 |
59 | @font-face {
60 | font-family: Poppins;
61 | src: url("../fonts/Poppins-Italic.woff2") format("woff2");
62 | font-weight: 400; /* Regular */
63 | font-style: italic;
64 | font-display: swap;
65 | }
66 |
67 | @font-face {
68 | font-family: Poppins;
69 | src: url("../fonts/Poppins-Medium.woff2") format("woff2");
70 | font-weight: 500; /* Medium */
71 | font-style: normal;
72 | font-display: swap;
73 | }
74 |
75 | @font-face {
76 | font-family: Poppins;
77 | src: url("../fonts/Poppins-MediumItalic.woff2") format("woff2");
78 | font-weight: 500; /* Medium */
79 | font-style: italic;
80 | font-display: swap;
81 | }
82 |
83 | @font-face {
84 | font-family: Poppins;
85 | src: url("../fonts/Poppins-SemiBold.woff2") format("woff2");
86 | font-weight: 600; /* SemiBold */
87 | font-style: normal;
88 | font-display: swap;
89 | }
90 |
91 | @font-face {
92 | font-family: Poppins;
93 | src: url("../fonts/Poppins-SemiBoldItalic.woff2") format("woff2");
94 | font-weight: 600; /* SemiBold */
95 | font-style: italic;
96 | font-display: swap;
97 | }
98 |
99 | @font-face {
100 | font-family: Poppins;
101 | src: url("../fonts/Poppins-Bold.woff2") format("woff2");
102 | font-weight: 700; /* Bold */
103 | font-style: normal;
104 | font-display: swap;
105 | }
106 |
107 | @font-face {
108 | font-family: Poppins;
109 | src: url("../fonts/Poppins-BoldItalic.woff2") format("woff2");
110 | font-weight: 700; /* Bold */
111 | font-style: italic;
112 | font-display: swap;
113 | }
114 |
115 | @font-face {
116 | font-family: Poppins;
117 | src: url("../fonts/Poppins-ExtraBold.woff2") format("woff2");
118 | font-weight: 800; /* ExtraBold */
119 | font-style: normal;
120 | font-display: swap;
121 | }
122 |
123 | @font-face {
124 | font-family: Poppins;
125 | src: url("../fonts/Poppins-ExtraBoldItalic.woff2") format("woff2");
126 | font-weight: 800; /* ExtraBold */
127 | font-style: italic;
128 | font-display: swap;
129 | }
130 |
131 | @font-face {
132 | font-family: Poppins;
133 | src: url("../fonts/Poppins-Black.woff2") format("woff2");
134 | font-weight: 900; /* Black */
135 | font-style: normal;
136 | font-display: swap;
137 | }
138 |
139 | @font-face {
140 | font-family: Poppins;
141 | src: url("../fonts/Poppins-BlackItalic.woff2") format("woff2");
142 | font-weight: 900; /* Black */
143 | font-style: italic;
144 | font-display: swap;
145 | }
146 |
147 | @font-face {
148 | font-family: "WDXL Lubrifont TC";
149 | src: url("../fonts/WDXLLubrifontTC-Regular.woff2") format("woff2");
150 | font-weight: 400;
151 | font-style: normal;
152 | font-display: swap;
153 | }
154 |
155 | h1 {
156 | font-weight: 600;
157 | letter-spacing: $high-spacing;
158 |
159 | @extend %as-h1;
160 |
161 | span {
162 | color: $secondary-color;
163 | }
164 | }
165 |
166 | h2 {
167 | @extend %as-h2;
168 | }
169 |
170 | h3 {
171 | @extend %as-h3;
172 | }
173 |
174 | h4 {
175 | @extend %as-h4;
176 | }
177 |
178 | h5 {
179 | @extend %as-h5;
180 | }
181 |
182 | h6 {
183 | @extend %as-h6;
184 | }
185 |
186 | h2,
187 | h3 {
188 | letter-spacing: $extreme-spacing;
189 | font-family: $wdxl-font;
190 | font-weight: 900;
191 | }
192 |
193 | h4,
194 | h5,
195 | h6 {
196 | letter-spacing: $medium-spacing;
197 | font-weight: 600;
198 | line-height: 1.5;
199 | }
200 |
201 | p,
202 | li {
203 | letter-spacing: $less-spacing;
204 |
205 | @extend %as-p;
206 | }
207 |
208 | a {
209 | display: inline;
210 |
211 | #blogtemplate &,
212 | #terms-conditions &,
213 | #privacy-policy & {
214 | overflow-wrap: break-word;
215 | }
216 | }
217 |
218 | a,
219 | button {
220 | @include smooth-trans;
221 | }
222 |
223 | b,
224 | strong {
225 | font-weight: 600;
226 | }
227 |
228 | strike {
229 | text-decoration: line-through;
230 | }
231 |
232 | u {
233 | text-decoration: underline;
234 | text-underline-offset: 0.125em;
235 | text-decoration-color: $secondary-color;
236 | }
237 |
238 | em,
239 | i {
240 | font-style: italic;
241 | }
242 |
--------------------------------------------------------------------------------
/src/components/layout/hero.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 |
3 | import { Link } from "gatsby";
4 | import { StaticImage } from "gatsby-plugin-image";
5 |
6 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
7 |
8 | import useTranslation from "../../hooks/use-translation";
9 | import useSiteMetadata from "../../hooks/use-site-metadata";
10 |
11 | import heroLogo from "../../images/logo/mnfx-icon.svg";
12 |
13 | import * as heroStyles from "../../styles/modules/layout/hero.module.scss";
14 |
15 | // TODO: klaar voor TS'en..
16 |
17 | const Hero = () => {
18 | const { t } = useTranslation();
19 |
20 | const { telephone } = useSiteMetadata();
21 |
22 | useEffect(() => {
23 | const handleScroll = () => {
24 | const bg = document.querySelector("[data-hero-bg]");
25 | if (bg) {
26 | bg.style.transform = `translateY(${window.scrollY * 0.4}px)`;
27 | }
28 | };
29 | window.addEventListener("scroll", handleScroll);
30 | return () => window.removeEventListener("scroll", handleScroll);
31 | }, []);
32 |
33 | return (
34 |
35 |
36 |
37 |
38 |
39 |

45 |
46 |
47 |
48 | {t("hero.menefex")}
49 | .{" "}
50 |
51 |
56 |
57 |
58 | -
59 |
63 | {t("hero.experience")}
64 |
65 | -
66 |
70 | {t("hero.affordable")}
71 |
72 | -
73 |
77 | {t("hero.fast")}
78 |
79 |
80 |
81 |
87 |
88 |
89 | {/*
*/}
90 |
91 |
101 |
102 |
103 |
138 |
139 |
140 |
141 | );
142 | };
143 | export default Hero;
144 |
--------------------------------------------------------------------------------
/src/styles/modules/ui/projects.module.scss:
--------------------------------------------------------------------------------
1 | @use "../../customs" as *;
2 |
3 | .projects {
4 | $projects: &;
5 |
6 | &-container {
7 | @include container;
8 |
9 | > div:first-child {
10 | margin-bottom: rem-calc(36);
11 | display: flex;
12 | justify-content: space-between;
13 | align-items: center;
14 | flex-wrap: wrap;
15 | gap: rem-calc(12 16);
16 |
17 | > h3 {
18 | @include section-title($align: center, $space: rem-calc(0));
19 | }
20 |
21 | > a {
22 | @include more-button;
23 | }
24 | }
25 |
26 | #{$projects}-wrapper {
27 | display: flex;
28 | flex-direction: column-reverse;
29 | gap: rem-calc(32);
30 |
31 | @include tablet {
32 | flex-direction: row;
33 | gap: rem-calc(64);
34 | }
35 |
36 | #{$projects}-content {
37 | flex: 1 1 32.5%;
38 | display: flex;
39 | flex-direction: column;
40 | justify-content: flex-start;
41 | min-height: rem-calc(430);
42 | height: fit-content;
43 | border-left: 5px solid $smoke-color;
44 | padding-left: rem-calc(36);
45 |
46 | @include compact {
47 | min-height: rem-calc(440);
48 | }
49 |
50 | @include mobile {
51 | min-height: rem-calc(390);
52 | }
53 |
54 | @include phablet {
55 | min-height: rem-calc(370);
56 | }
57 |
58 | @include tablet {
59 | min-width: rem-calc(360);
60 | min-height: rem-calc(460);
61 | }
62 |
63 | #{$projects}-logo {
64 | width: fit-content;
65 | margin-bottom: rem-calc(24);
66 | padding: rem-calc(8);
67 |
68 | @include border(rgba($smoke-color, 0.5));
69 | }
70 |
71 | > h4 {
72 | margin-bottom: rem-calc(16);
73 |
74 | @include fluid-typing(22, 26);
75 | }
76 |
77 | > p {
78 | margin-bottom: rem-calc(16);
79 |
80 | @extend %as-medium;
81 | }
82 |
83 | > span {
84 | font-weight: 700;
85 | margin-bottom: rem-calc(8);
86 | }
87 |
88 | > ul {
89 | display: flex;
90 | gap: rem-calc(6);
91 | flex-wrap: wrap;
92 | margin-bottom: rem-calc(36);
93 |
94 | > li {
95 | background: linear-gradient(
96 | 145deg,
97 | $primary-color,
98 | $dark-color
99 | );
100 | border-radius: $low-radius;
101 | padding: rem-calc(2 12);
102 | width: fit-content;
103 | color: $smoke-color;
104 | white-space: nowrap;
105 |
106 | @include fluid-typing(10, 11);
107 | }
108 | }
109 |
110 | #{$projects}-buttons {
111 | display: flex;
112 | flex-wrap: wrap;
113 | gap: rem-calc(16);
114 |
115 | > a {
116 | white-space: nowrap;
117 |
118 | &[target="_blank"] {
119 | @include primary-button(2);
120 | }
121 |
122 | &:not([target="_blank"]) {
123 | @include primary-button(1);
124 | }
125 | }
126 | }
127 | }
128 |
129 | #{$projects}-slider {
130 | flex: 1 1 67.5%;
131 | min-width: 0;
132 |
133 | div [data-gatsby-image-wrapper],
134 | a [data-gatsby-image-wrapper] {
135 | img {
136 | border-radius: $medium-radius;
137 | aspect-ratio: 16 / 9;
138 |
139 | @include border($smoke-color);
140 | }
141 | }
142 |
143 | a [data-gatsby-image-wrapper] {
144 | img {
145 | @include smooth-trans;
146 | }
147 |
148 | &:hover {
149 | img {
150 | border-color: $link-color;
151 | }
152 | }
153 | }
154 |
155 | #{$projects}-navigation {
156 | margin-top: rem-calc(24);
157 | display: flex;
158 | justify-content: space-between;
159 | align-items: center;
160 |
161 | > div {
162 | width: fit-content;
163 | }
164 |
165 | > button {
166 | background-color: #00000020;
167 | border-radius: $low-radius;
168 | width: rem-calc(32);
169 | height: rem-calc(32);
170 | display: flex;
171 | justify-content: center;
172 | align-items: center;
173 |
174 | &:hover {
175 | background-color: $lightgrey-color;
176 | }
177 | }
178 | }
179 | }
180 | }
181 | }
182 |
183 | &-no-image {
184 | p {
185 | display: block;
186 | max-height: rem-calc(500);
187 | height: 100%;
188 | }
189 | }
190 | }
191 |
--------------------------------------------------------------------------------