diff --git a/README.md b/README.md index 612e059..870b753 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,19 @@ Landing page for continuum-3d.com. Eleventy + Pug + Tailwind CSS, with a small JSON-based i18n setup (`src/locales/en.json`, `src/locales/pl.json`). -- English lives at `src/en/index.pug` and builds to `/`. -- Polish lives at `src/pl/index.pug` and builds to `/pl/`. -- Add more languages by adding `src/locales/.json` and a - `src//index.pug` with `lang: ` in its front matter. +- English (the default language) lives at `src/en/index.pug` and builds to `/`. +- Every other language lives at `src//index.pug` and builds to `//` + (e.g. Polish: `src/pl/index.pug` -> `/pl/`). +- The page markup is shared in `src/_includes/home.pug`; all copy is in + `src/locales/.json`. +- To add a language: copy `src/locales/en.json` to `src/locales/.json`, + translate it (including `lang_name`, the language's own name), and add a + `src//index.pug` with `lang: ` and `permalink: //` in its + front matter that includes `../_includes/home.pug`. The footer language + switcher and the `hreflang` tags pick it up automatically. +- Site-wide settings (domain, contact address, default language) are in + `src/_data/site.json`. The contact form opens the visitor's mail client + addressed to `contactEmail`. ## Develop diff --git a/eleventy.config.js b/eleventy.config.js index 4ebab28..c8a35fe 100644 --- a/eleventy.config.js +++ b/eleventy.config.js @@ -2,6 +2,7 @@ import pug from "pug"; export default function (eleventyConfig) { eleventyConfig.addPassthroughCopy("src/assets/img"); + eleventyConfig.addPassthroughCopy("src/assets/fonts"); eleventyConfig.addExtension("pug", { compile: (inputContent, inputPath) => { diff --git a/package-lock.json b/package-lock.json index e4c623f..6dea384 100644 --- a/package-lock.json +++ b/package-lock.json @@ -622,7 +622,6 @@ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -1518,7 +1517,6 @@ "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "dev": true, "license": "MIT", - "peer": true, "bin": { "jiti": "bin/jiti.js" } @@ -2077,7 +2075,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.16", "picocolors": "^1.1.1", @@ -2227,7 +2224,6 @@ "integrity": "sha512-7Hc+IvlQ7hlaIfQFZnxlRl0jnpWq2qwibORBhQYIb0QbNtuicc5ZxvKkVT71HJ4Py1wSZ/3VR1r8LfkCtoCzhw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "posthtml-parser": "^0.11.0", "posthtml-render": "^3.0.0" diff --git a/src/_data/languages.js b/src/_data/languages.js new file mode 100644 index 0000000..61ac40c --- /dev/null +++ b/src/_data/languages.js @@ -0,0 +1,27 @@ +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import site from "./site.json" with { type: "json" }; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +// One entry per src/locales/.json. The default language is served from +// "/", every other one from "//". Order: default first, then alphabetical. +export default function () { + const localesDir = path.join(__dirname, "..", "locales"); + return fs + .readdirSync(localesDir) + .filter((file) => file.endsWith(".json")) + .map((file) => { + const code = path.basename(file, ".json"); + const dict = JSON.parse(fs.readFileSync(path.join(localesDir, file), "utf8")); + return { + code, + name: dict.lang_name, + href: code === site.defaultLang ? "/" : `/${code}/`, + }; + }) + .sort((a, b) => + a.code === site.defaultLang ? -1 : b.code === site.defaultLang ? 1 : a.code.localeCompare(b.code) + ); +} diff --git a/src/_data/site.json b/src/_data/site.json new file mode 100644 index 0000000..9bfb737 --- /dev/null +++ b/src/_data/site.json @@ -0,0 +1,5 @@ +{ + "url": "https://continuum-3d.com", + "contactEmail": "contact@continuum-3d.com", + "defaultLang": "en" +} diff --git a/src/_includes/home.pug b/src/_includes/home.pug new file mode 100644 index 0000000..0e778af --- /dev/null +++ b/src/_includes/home.pug @@ -0,0 +1,96 @@ +- const t = i18n[lang] +- const gcode = ";WIPE_START\nG1 F1800\nG1 X81.8 Y92.209 E-.31416\nG1 X81.814 Y91.724 E-.36426\nG1 X81.821 Y91.752 E-.02197\nG1 X81.847 Y91.813 E-.04961\n;WIPE_END\nG1 X81.82 Y88.155 Z.4 F24000\nG1 Z.2\nG1 E2.5 F1500\nG1 F1800\nG1 X81.797 Y87.721 E.0165\nG1 X82.12 Y87.733 E.01227\nG1 X82.349 Y87.734 E.00874\nG1 X82.161 Y87.777 E.00734\nG1 X81.955 Y87.909 E.00929\nG1 X81.884 Y88 E.00439\nG1 X81.835 Y88.118 E.00486\nG1 E-1.75 F2700\n;WIPE_START\nG1 F1800\nG1 X81.797 Y87.721 E-.29884\nG1 X82.12 Y87.733 E-.24219\nG1 X82.349 Y87.734 E-.1724\nG1 X82.302 Y87.745 E-.03658\n;WIPE_END" + +//- Decorative G-code text block. Positions are in design px, relative to the section. +mixin gcode(style, color, align) + pre(aria-hidden="true" class="hidden lg:block absolute w-[214px] m-0 font-poppins text-[14px] leading-[1.2] whitespace-pre select-none pointer-events-none " + (align === "right" ? "text-right" : "text-left") style=style + ";color:" + color)= gcode + +//- Decorative blurred glow ellipse. +mixin glow(left, top, w, h, rot) + div(aria-hidden="true" class="hidden lg:block absolute rounded-full pointer-events-none blur-[60px]" style=`left:${left}px;top:${top}px;width:${w}px;height:${h}px;transform:rotate(${rot}deg);background:linear-gradient(148.5deg,#5272d3 0%,#15164e 42%,#15164e 96%)`) + +header(class="relative z-20 h-[68px] border-b border-accent") + div(class="mx-auto flex h-full max-w-[962px] items-center justify-between pl-4 pr-4 lg:pl-[49px] lg:pr-[60px]") + a(href="#top" class="flex items-center gap-[18px]") + span(class="relative block h-[60px] w-[57px] shrink-0") + img(src="/assets/img/logo-mark.svg" alt=t.logo_alt class="absolute left-[-27px] top-[-27px] h-[126px] w-[123px] max-w-none") + span(class="hidden font-chillax text-[24px] sm:inline font-bold capitalize leading-[42px] tracking-[4px] text-white") Continuum + nav(aria-label="Main") + ul(class="m-0 flex list-none gap-4 p-0 text-[14px] leading-[1.2] lg:gap-[50px] lg:text-[16px]") + li: a(href="#about" class="text-accent hover:text-white")= t.nav_about + li: a(href="#products" class="text-accent hover:text-white")= t.nav_products + li: a(href="#contact" class="text-accent hover:text-white")= t.nav_contact + +main(id="top" class="relative overflow-x-clip font-poppins") + div(class="relative mx-auto max-w-[962px]") + //- Vertical rule + div(aria-hidden="true" class="absolute bottom-0 left-4 top-0 z-10 w-px bg-accent lg:left-[41px]") + + //- Hero + section(class="relative px-10 py-24 lg:h-[552px] lg:p-0") + +gcode("right:19px;top:37px", "rgb(116 139 215 / 0.3)", "right") + h1(class="m-0 font-roboto-flex text-[22px] font-normal leading-[56px] tracking-[0.2em] sm:text-[28px] sm:leading-[72px] sm:tracking-[0.22em] text-white lg:absolute lg:left-[162px] lg:top-[186px] lg:text-[36px] lg:tracking-[8px]") + span(class="block lg:w-[475px] lg:text-center")= t.hero_line_1 + span(class="block lg:ml-[166px] lg:mt-[-3px] lg:w-[475px] lg:text-center")= t.hero_line_2 + + //- About + section(id="about" class="relative px-10 py-16 lg:h-[390px] lg:p-0") + +gcode("left:62px;top:-62px", "rgb(82 114 211 / 0.4)", "left") + div(aria-hidden="true" class="absolute hidden rounded-full lg:block" style="left:117px;top:21px;width:318px;height:318px;background:linear-gradient(150deg,#000 0%,#15164e 42%,#000 100%)") + +glow(780, 111, 286, 101, 301) + +glow(852, 269, 230, 107, 40) + +glow(-112, 315, 286, 101, 350) + div(class="relative z-10 lg:absolute lg:left-[464px] lg:top-[100px] lg:w-[387px]") + h2(class="m-0 text-[18px] font-normal leading-[1.2] text-[#c0ceff]")= t.about_title + p(class="m-0 mt-10 text-[16px] leading-[1.2] text-[#abbfff] lg:mt-[45px]")= t.about_text + + //- Products + section(id="products" class="relative px-10 py-16 text-center lg:h-[577px] lg:p-0") + div(aria-hidden="true" class="absolute hidden lg:block pointer-events-none blur-[70px]" style="left:-83px;top:2px;width:1069px;height:138px;transform:rotate(3deg);background:linear-gradient(180deg,#7186c6 0%,#15164e 28%,#15164e 46%,#000 100%)") + +glow(595, 49, 286, 101, 350) + img(src="/assets/img/wave.svg" alt="" aria-hidden="true" class="pointer-events-none absolute hidden max-w-none lg:block" style="left:166px;top:-156px;width:1034px;height:775px") + +gcode("left:62px;top:283px", "rgb(116 139 215 / 0.3)", "left") + div(class="relative z-10 lg:absolute lg:inset-x-0 lg:top-[150px]") + h2(class="m-0 text-[18px] font-normal leading-[1.2] text-white")= t.products_title + p(class="m-0 mt-16 text-[16px] leading-[1.2] text-white lg:mt-[116px]")= t.products_line_1 + p(class="m-0 mt-6 text-[16px] leading-[1.2] text-white lg:mt-[40px]")= t.products_line_2 + + //- Contact + section(id="contact" class="relative px-10 py-16 lg:h-[429px] lg:p-0") + div(aria-hidden="true" class="absolute inset-0 hidden overflow-hidden lg:block") + div(class="absolute" style="left:38px;top:0;width:1088px;height:429px;background:linear-gradient(90deg,rgb(82 114 211 / 0.3) 0%,rgb(82 114 211 / 0) 100%)") + +glow(-39, 248, 286, 101, 58) + +glow(68, 259, 286, 101, 58) + +gcode("right:19px;top:207px", "rgb(116 139 215 / 0.3)", "right") + form(id="contact-form" class="relative z-10 flex flex-col gap-6 lg:static lg:block" action="mailto:" + site.contactEmail method="post" enctype="text/plain" data-to=site.contactEmail data-subject=t.form_subject) + label(class="order-4 block lg:absolute lg:left-[94px] lg:top-[58px] lg:w-[412px]") + span(class="sr-only")= t.form_message + textarea(name="message" required placeholder=t.form_message class="block h-[306px] w-full resize-none rounded-[13px] border-2 border-accent bg-transparent p-4 text-[16px] text-white placeholder:text-accent focus:outline-none focus:ring-2 focus:ring-accent") + div(class="contents lg:block lg:static") + h2(class="order-1 m-0 text-[18px] font-normal leading-[1.2] text-white lg:absolute lg:left-[594px] lg:top-[93px]")= t.contact_title + label(class="order-2 block lg:absolute lg:left-[593px] lg:top-[176px] lg:w-[214px]") + span(class="sr-only")= t.form_name + input(type="text" name="name" required autocomplete="name" placeholder=t.form_name class="block h-[29px] w-full rounded-[13px] border-2 border-accent bg-transparent px-3 text-[14px] text-white placeholder:text-accent focus:outline-none focus:ring-2 focus:ring-accent") + label(class="order-3 block lg:absolute lg:left-[594px] lg:top-[229px] lg:w-[214px]") + span(class="sr-only")= t.form_email + input(type="email" name="email" required autocomplete="email" placeholder=t.form_email class="block h-[29px] w-full rounded-[13px] border-2 border-accent bg-transparent px-3 text-[14px] text-white placeholder:text-accent focus:outline-none focus:ring-2 focus:ring-accent") + button(type="submit" class="order-5 h-[30px] w-[100px] rounded-[13px] border-2 border-accent bg-transparent text-[14px] text-white hover:bg-accent focus:outline-none focus:ring-2 focus:ring-accent lg:absolute lg:left-[594px] lg:top-[276px]")= t.form_send + + //- Footer + footer(class="relative z-10 flex h-[139px] flex-col items-center justify-center gap-3 text-xs text-gray-500") + nav(aria-label=t.lang_label) + ul(class="m-0 flex list-none flex-wrap justify-center gap-x-4 gap-y-1 p-0") + each l in languages + li + a(href=l.href lang=l.code hreflang=l.code aria-current=(l.code === lang ? "page" : false) class=(l.code === lang ? "text-white" : "underline underline-offset-4 hover:text-gray-300"))= l.name + p(class="m-0")= t.footer_text + +script. + // No backend: compose a mailto: link from the form and hand it to the visitor's mail client. + document.getElementById("contact-form").addEventListener("submit", function (e) { + e.preventDefault(); + var f = e.currentTarget, d = new FormData(f); + var subject = f.dataset.subject + " - " + d.get("name"); + var body = d.get("message") + "\n\n" + d.get("name") + " <" + d.get("email") + ">"; + window.location.href = "mailto:" + f.dataset.to + "?subject=" + encodeURIComponent(subject) + "&body=" + encodeURIComponent(body); + }); diff --git a/src/_includes/layout.pug b/src/_includes/layout.pug index 0c08be7..fb4f29a 100644 --- a/src/_includes/layout.pug +++ b/src/_includes/layout.pug @@ -5,5 +5,8 @@ html(lang=lang) meta(name="viewport" content="width=device-width, initial-scale=1.0") title= i18n[lang].meta_title link(rel="stylesheet" href="/assets/css/style.css") - body(class="bg-gray-950 text-white antialiased") + each l in languages + link(rel="alternate" hreflang=l.code href=site.url + l.href) + link(rel="alternate" hreflang="x-default" href=site.url + "/") + body(class="bg-black font-poppins text-white antialiased") != content diff --git a/src/assets/css/tailwind.css b/src/assets/css/tailwind.css index b5c61c9..c8d562f 100644 --- a/src/assets/css/tailwind.css +++ b/src/assets/css/tailwind.css @@ -1,3 +1,43 @@ @tailwind base; @tailwind components; @tailwind utilities; + +@font-face { + font-family: "Poppins"; + font-weight: 400; + font-display: swap; + src: url("/assets/fonts/poppins-latin-ext-400-normal.woff2") format("woff2"); + unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +@font-face { + font-family: "Poppins"; + font-weight: 400; + font-display: swap; + src: url("/assets/fonts/poppins-latin-400-normal.woff2") format("woff2"); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +@font-face { + font-family: "Roboto Flex"; + font-weight: 100 1000; + font-display: swap; + src: url("/assets/fonts/roboto-flex-latin-ext-wght-normal.woff2") format("woff2"); + unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +@font-face { + font-family: "Roboto Flex"; + font-weight: 100 1000; + font-display: swap; + src: url("/assets/fonts/roboto-flex-latin-wght-normal.woff2") format("woff2"); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +@font-face { + font-family: "Chillax"; + font-weight: 700; + font-display: swap; + src: url("/assets/fonts/chillax-700.woff2") format("woff2"); +} + +html { + scroll-behavior: smooth; + scroll-padding-top: 68px; +} diff --git a/src/assets/fonts/chillax-700.woff2 b/src/assets/fonts/chillax-700.woff2 new file mode 100644 index 0000000..46b7772 Binary files /dev/null and b/src/assets/fonts/chillax-700.woff2 differ diff --git a/src/assets/fonts/poppins-latin-400-normal.woff2 b/src/assets/fonts/poppins-latin-400-normal.woff2 new file mode 100644 index 0000000..b69e009 Binary files /dev/null and b/src/assets/fonts/poppins-latin-400-normal.woff2 differ diff --git a/src/assets/fonts/poppins-latin-ext-400-normal.woff2 b/src/assets/fonts/poppins-latin-ext-400-normal.woff2 new file mode 100644 index 0000000..63f4711 Binary files /dev/null and b/src/assets/fonts/poppins-latin-ext-400-normal.woff2 differ diff --git a/src/assets/fonts/roboto-flex-latin-ext-wght-normal.woff2 b/src/assets/fonts/roboto-flex-latin-ext-wght-normal.woff2 new file mode 100644 index 0000000..6524d56 Binary files /dev/null and b/src/assets/fonts/roboto-flex-latin-ext-wght-normal.woff2 differ diff --git a/src/assets/fonts/roboto-flex-latin-wght-normal.woff2 b/src/assets/fonts/roboto-flex-latin-wght-normal.woff2 new file mode 100644 index 0000000..88bbf49 Binary files /dev/null and b/src/assets/fonts/roboto-flex-latin-wght-normal.woff2 differ diff --git a/src/assets/img/logo-mark.svg b/src/assets/img/logo-mark.svg new file mode 100644 index 0000000..9526ce8 --- /dev/null +++ b/src/assets/img/logo-mark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/img/wave.svg b/src/assets/img/wave.svg new file mode 100644 index 0000000..12211a1 --- /dev/null +++ b/src/assets/img/wave.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/en/index.pug b/src/en/index.pug index 429d70a..0b543b5 100644 --- a/src/en/index.pug +++ b/src/en/index.pug @@ -3,10 +3,4 @@ layout: layout.pug lang: en permalink: / --- -section(class="min-h-screen flex flex-col items-center justify-center px-6 text-center") - h1(class="text-5xl font-bold tracking-tight mb-4")= i18n[lang].hero_title - p(class="text-lg text-gray-400 max-w-xl mb-10")= i18n[lang].hero_subtitle - a(href="/pl/" class="text-sm text-gray-500 hover:text-gray-300 underline underline-offset-4")= i18n[lang].lang_switch_label - -footer(class="pb-8 text-center text-xs text-gray-600") - p= i18n[lang].footer_text +include ../_includes/home.pug diff --git a/src/locales/en.json b/src/locales/en.json index ff06d9a..4cc61b1 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1,10 +1,23 @@ { - "meta_title": "Continuum 3D", - "nav_home": "Home", - "hero_title": "Continuum 3D", - "hero_subtitle": "3D printing engineering and manufacturing solutions.", - "cta_text": "Contact us", - "lang_switch_label": "Polski", - "lang_switch_href": "/pl/", - "footer_text": "Continuum 3D Sp. z o.o." + "meta_title": "Continuum", + "nav_about": "About us", + "nav_products": "Products", + "nav_contact": "Contact", + "hero_line_1": "Production", + "hero_line_2": "without limits", + "about_title": "Who are we?", + "about_text": "We are an interdisciplinary team who met at university with shared interests and the aim of building something of our own, answering market needs and other people's problems.", + "products_title": "Products", + "products_line_1": "We are preparing something extraordinary", + "products_line_2": "Coming soon", + "contact_title": "Get in touch!", + "form_name": "Full name", + "form_email": "Email", + "form_message": "Message", + "form_send": "Send", + "logo_alt": "Continuum logo", + "footer_text": "Continuum 3D Sp. z o.o.", + "lang_name": "English", + "lang_label": "Language", + "form_subject": "Message from the website" } diff --git a/src/locales/pl.json b/src/locales/pl.json index ad9b5b6..25a57a7 100644 --- a/src/locales/pl.json +++ b/src/locales/pl.json @@ -1,10 +1,23 @@ { - "meta_title": "Continuum 3D", - "nav_home": "Start", - "hero_title": "Continuum 3D", - "hero_subtitle": "Inżynieria i produkcja w druku 3D.", - "cta_text": "Skontaktuj się", - "lang_switch_label": "English", - "lang_switch_href": "/", - "footer_text": "Continuum 3D Sp. z o.o." + "meta_title": "Continuum", + "nav_about": "O nas", + "nav_products": "Produkty", + "nav_contact": "Kontakt", + "hero_line_1": "Production", + "hero_line_2": "without limits", + "about_title": "Kim jesteśmy?", + "about_text": "Jesteśmy interdyscyplinarnym zespołem, które spotkały się na studiach z wspólnymi zainteresowaniami i zamiarem zbudowania czegoś własnego, odpowiadającego na potrzeby rynku i problemy innych.", + "products_title": "Produkty", + "products_line_1": "Szykujemy coś niezwykłego", + "products_line_2": "Już niedługo", + "contact_title": "Skontaktuj się z nami!", + "form_name": "Imię i nazwisko", + "form_email": "E-mail", + "form_message": "Wiadomość", + "form_send": "Wyślij", + "logo_alt": "Logo Continuum", + "footer_text": "Continuum 3D Sp. z o.o.", + "lang_name": "Polski", + "lang_label": "Język", + "form_subject": "Wiadomość ze strony" } diff --git a/src/pl/index.pug b/src/pl/index.pug index f4ec306..44e652e 100644 --- a/src/pl/index.pug +++ b/src/pl/index.pug @@ -3,10 +3,4 @@ layout: layout.pug lang: pl permalink: /pl/ --- -section(class="min-h-screen flex flex-col items-center justify-center px-6 text-center") - h1(class="text-5xl font-bold tracking-tight mb-4")= i18n[lang].hero_title - p(class="text-lg text-gray-400 max-w-xl mb-10")= i18n[lang].hero_subtitle - a(href="/" class="text-sm text-gray-500 hover:text-gray-300 underline underline-offset-4")= i18n[lang].lang_switch_label - -footer(class="pb-8 text-center text-xs text-gray-600") - p= i18n[lang].footer_text +include ../_includes/home.pug diff --git a/tailwind.config.js b/tailwind.config.js index bc9fa3f..7cc65c7 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,7 +1,18 @@ module.exports = { content: ["./src/**/*.pug"], theme: { - extend: {}, + extend: { + colors: { + accent: "#5272d3", + deep: "#15164e", + "accent-light": "#748bd7", + }, + fontFamily: { + poppins: ["Poppins", "sans-serif"], + "roboto-flex": ['"Roboto Flex"', "sans-serif"], + chillax: ["Chillax", "sans-serif"], + }, + }, }, plugins: [], };