From fcc2f4704e39b0e69b377cc138f75027721dac22 Mon Sep 17 00:00:00 2001 From: Dawid Rycerz Date: Tue, 22 Jul 2025 15:08:37 +0300 Subject: Initial template --- .editorconfig | 12 + .gitattributes | 19 + .github/workflows/actions.yaml | 41 + .gitignore | 24 + .npmrc | 2 + .prettierignore | 4 + .prettierrc.cjs | 13 + .stackblitzrc | 6 + .vscode/astrowind/config-schema.json | 275 + .vscode/extensions.json | 10 + .vscode/launch.json | 11 + .vscode/settings.json | 15 + LICENSE.md | 21 + README.md | 95 + astro.config.ts | 94 + eslint.config.js | 59 + package-lock.json | 11686 ++++++++++++++++++++ package.json | 69 + public/_headers | 2 + public/robots.txt | 2 + sandbox.config.json | 11 + src/assets/favicons/apple-touch-icon.png | Bin 0 -> 26374 bytes src/assets/favicons/favicon.ico | Bin 0 -> 15086 bytes src/assets/favicons/favicon.png | Bin 0 -> 1403 bytes src/assets/favicons/favicon.svg | 20 + src/assets/images/app-store.png | Bin 0 -> 11251 bytes src/assets/images/ceramic-coating.svg | 18 + src/assets/images/color-change.svg | 44 + src/assets/images/content-image-new.webp | Bin 0 -> 226484 bytes src/assets/images/content-image.webp | Bin 0 -> 86184 bytes src/assets/images/content-image2-new.webp | Bin 0 -> 297522 bytes src/assets/images/content-image2.webp | Bin 0 -> 11238 bytes src/assets/images/customworks-hero.webp | Bin 0 -> 119260 bytes src/assets/images/customworks-logo.png | Bin 0 -> 18151 bytes src/assets/images/engine-cleaning.svg | 2 + src/assets/images/google-play.png | Bin 0 -> 13307 bytes src/assets/images/hero-image.jpg | Bin 0 -> 190587 bytes src/assets/images/interior-detailing.svg | 16 + src/assets/images/invisible-wipers.svg | 20 + src/assets/images/paint-restoration.svg | 2 + src/assets/images/ppf-protection.svg | 30 + src/assets/images/rzetelna-firma-logo.png | Bin 0 -> 3679 bytes src/assets/images/visual-tuning.svg | 2 + src/assets/styles/tailwind.css | 95 + src/components/CustomStyles.astro | 69 + src/components/Favicons.astro | 10 + src/components/Logo.astro | 9 + src/components/blog/Grid.astro | 14 + src/components/blog/GridItem.astro | 71 + src/components/blog/Headline.astro | 12 + src/components/blog/List.astro | 20 + src/components/blog/ListItem.astro | 120 + src/components/blog/Pagination.astro | 36 + src/components/blog/RelatedPosts.astro | 31 + src/components/blog/SinglePost.astro | 103 + src/components/blog/Tags.astro | 43 + src/components/blog/ToBlogLink.astro | 20 + src/components/common/Analytics.astro | 13 + src/components/common/ApplyColorMode.astro | 33 + src/components/common/BasicScripts.astro | 255 + src/components/common/CommonMeta.astro | 8 + src/components/common/Image.astro | 61 + src/components/common/Metadata.astro | 68 + src/components/common/SiteVerification.astro | 5 + src/components/common/SocialShare.astro | 65 + src/components/common/SplitbeeAnalytics.astro | 6 + src/components/common/ToggleMenu.astro | 29 + src/components/common/ToggleTheme.astro | 28 + src/components/ui/Background.astro | 11 + src/components/ui/Button.astro | 40 + src/components/ui/DListItem.astro | 22 + src/components/ui/Form.astro | 87 + src/components/ui/Headline.astro | 35 + src/components/ui/ItemGrid.astro | 65 + src/components/ui/ItemGrid2.astro | 59 + src/components/ui/Timeline.astro | 60 + src/components/ui/WidgetWrapper.astro | 34 + src/components/widgets/Announcement.astro | 16 + src/components/widgets/BlogHighlightedPosts.astro | 64 + src/components/widgets/BlogLatestPosts.astro | 63 + src/components/widgets/Brands.astro | 38 + src/components/widgets/CallToAction.astro | 58 + src/components/widgets/CallToActionImage.astro | 73 + src/components/widgets/Contact.astro | 40 + src/components/widgets/Content.astro | 94 + src/components/widgets/FAQs.astro | 33 + src/components/widgets/Features.astro | 36 + src/components/widgets/Features2.astro | 38 + src/components/widgets/Features3.astro | 70 + src/components/widgets/Footer.astro | 61 + src/components/widgets/Header.astro | 14 + src/components/widgets/Hero.astro | 99 + src/components/widgets/Hero2.astro | 99 + src/components/widgets/HeroText.astro | 86 + src/components/widgets/Note.astro | 23 + src/components/widgets/Pricing.astro | 83 + src/components/widgets/Stats.astro | 46 + src/components/widgets/Steps.astro | 59 + src/components/widgets/Steps2.astro | 79 + src/components/widgets/Testimonials.astro | 75 + src/config.yaml | 68 + src/content/config.ts | 70 + src/env.d.ts | 5 + src/layouts/LandingLayout.astro | 30 + src/layouts/Layout.astro | 48 + src/layouts/MarkdownLayout.astro | 28 + src/layouts/PageLayout.astro | 27 + src/navigation.ts | 130 + src/pages/404.astro | 24 + src/pages/index.astro | 195 + src/pages/polityka-prywatnosci.md | 37 + src/types.d.ts | 281 + src/utils/blog.ts | 281 + src/utils/directories.ts | 18 + src/utils/frontmatter.ts | 50 + src/utils/images-optimization.ts | 351 + src/utils/images.ts | 111 + src/utils/permalinks.ts | 134 + src/utils/utils.ts | 52 + tailwind.config.js | 41 + tsconfig.json | 13 + vendor/README.md | 4 + vendor/integration/index.ts | 116 + vendor/integration/types.d.ts | 10 + vendor/integration/utils/configBuilder.ts | 203 + vendor/integration/utils/loadConfig.ts | 16 + vercel.json | 15 + vscode.tailwind.json | 17 + 128 files changed, 17984 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .github/workflows/actions.yaml create mode 100644 .gitignore create mode 100644 .npmrc create mode 100644 .prettierignore create mode 100644 .prettierrc.cjs create mode 100644 .stackblitzrc create mode 100644 .vscode/astrowind/config-schema.json create mode 100644 .vscode/extensions.json create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 astro.config.ts create mode 100644 eslint.config.js create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 public/_headers create mode 100644 public/robots.txt create mode 100644 sandbox.config.json create mode 100644 src/assets/favicons/apple-touch-icon.png create mode 100644 src/assets/favicons/favicon.ico create mode 100644 src/assets/favicons/favicon.png create mode 100644 src/assets/favicons/favicon.svg create mode 100644 src/assets/images/app-store.png create mode 100644 src/assets/images/ceramic-coating.svg create mode 100644 src/assets/images/color-change.svg create mode 100644 src/assets/images/content-image-new.webp create mode 100644 src/assets/images/content-image.webp create mode 100644 src/assets/images/content-image2-new.webp create mode 100644 src/assets/images/content-image2.webp create mode 100644 src/assets/images/customworks-hero.webp create mode 100644 src/assets/images/customworks-logo.png create mode 100644 src/assets/images/engine-cleaning.svg create mode 100644 src/assets/images/google-play.png create mode 100644 src/assets/images/hero-image.jpg create mode 100644 src/assets/images/interior-detailing.svg create mode 100644 src/assets/images/invisible-wipers.svg create mode 100644 src/assets/images/paint-restoration.svg create mode 100644 src/assets/images/ppf-protection.svg create mode 100644 src/assets/images/rzetelna-firma-logo.png create mode 100644 src/assets/images/visual-tuning.svg create mode 100644 src/assets/styles/tailwind.css create mode 100644 src/components/CustomStyles.astro create mode 100644 src/components/Favicons.astro create mode 100644 src/components/Logo.astro create mode 100644 src/components/blog/Grid.astro create mode 100644 src/components/blog/GridItem.astro create mode 100644 src/components/blog/Headline.astro create mode 100644 src/components/blog/List.astro create mode 100644 src/components/blog/ListItem.astro create mode 100644 src/components/blog/Pagination.astro create mode 100644 src/components/blog/RelatedPosts.astro create mode 100644 src/components/blog/SinglePost.astro create mode 100644 src/components/blog/Tags.astro create mode 100644 src/components/blog/ToBlogLink.astro create mode 100644 src/components/common/Analytics.astro create mode 100644 src/components/common/ApplyColorMode.astro create mode 100644 src/components/common/BasicScripts.astro create mode 100644 src/components/common/CommonMeta.astro create mode 100644 src/components/common/Image.astro create mode 100644 src/components/common/Metadata.astro create mode 100644 src/components/common/SiteVerification.astro create mode 100644 src/components/common/SocialShare.astro create mode 100644 src/components/common/SplitbeeAnalytics.astro create mode 100644 src/components/common/ToggleMenu.astro create mode 100644 src/components/common/ToggleTheme.astro create mode 100644 src/components/ui/Background.astro create mode 100644 src/components/ui/Button.astro create mode 100644 src/components/ui/DListItem.astro create mode 100644 src/components/ui/Form.astro create mode 100644 src/components/ui/Headline.astro create mode 100644 src/components/ui/ItemGrid.astro create mode 100644 src/components/ui/ItemGrid2.astro create mode 100644 src/components/ui/Timeline.astro create mode 100644 src/components/ui/WidgetWrapper.astro create mode 100644 src/components/widgets/Announcement.astro create mode 100644 src/components/widgets/BlogHighlightedPosts.astro create mode 100644 src/components/widgets/BlogLatestPosts.astro create mode 100644 src/components/widgets/Brands.astro create mode 100644 src/components/widgets/CallToAction.astro create mode 100644 src/components/widgets/CallToActionImage.astro create mode 100644 src/components/widgets/Contact.astro create mode 100644 src/components/widgets/Content.astro create mode 100644 src/components/widgets/FAQs.astro create mode 100644 src/components/widgets/Features.astro create mode 100644 src/components/widgets/Features2.astro create mode 100644 src/components/widgets/Features3.astro create mode 100644 src/components/widgets/Footer.astro create mode 100644 src/components/widgets/Header.astro create mode 100644 src/components/widgets/Hero.astro create mode 100644 src/components/widgets/Hero2.astro create mode 100644 src/components/widgets/HeroText.astro create mode 100644 src/components/widgets/Note.astro create mode 100644 src/components/widgets/Pricing.astro create mode 100644 src/components/widgets/Stats.astro create mode 100644 src/components/widgets/Steps.astro create mode 100644 src/components/widgets/Steps2.astro create mode 100644 src/components/widgets/Testimonials.astro create mode 100644 src/config.yaml create mode 100644 src/content/config.ts create mode 100644 src/env.d.ts create mode 100644 src/layouts/LandingLayout.astro create mode 100644 src/layouts/Layout.astro create mode 100644 src/layouts/MarkdownLayout.astro create mode 100644 src/layouts/PageLayout.astro create mode 100644 src/navigation.ts create mode 100644 src/pages/404.astro create mode 100644 src/pages/index.astro create mode 100644 src/pages/polityka-prywatnosci.md create mode 100644 src/types.d.ts create mode 100644 src/utils/blog.ts create mode 100644 src/utils/directories.ts create mode 100644 src/utils/frontmatter.ts create mode 100644 src/utils/images-optimization.ts create mode 100644 src/utils/images.ts create mode 100644 src/utils/permalinks.ts create mode 100644 src/utils/utils.ts create mode 100644 tailwind.config.js create mode 100644 tsconfig.json create mode 100644 vendor/README.md create mode 100644 vendor/integration/index.ts create mode 100644 vendor/integration/types.d.ts create mode 100644 vendor/integration/utils/configBuilder.ts create mode 100644 vendor/integration/utils/loadConfig.ts create mode 100644 vercel.json create mode 100644 vscode.tailwind.json diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..8927e2c --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +# https://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = false \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..2f88106 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,19 @@ +*.webp filter=lfs diff=lfs merge=lfs -text +*.jpg filter=lfs diff=lfs merge=lfs -text +*.svg filter=lfs diff=lfs merge=lfs -text +*.ICO filter=lfs diff=lfs merge=lfs -text +*.WEBP filter=lfs diff=lfs merge=lfs -text +*.JPG filter=lfs diff=lfs merge=lfs -text +*.jpeg filter=lfs diff=lfs merge=lfs -text +*.JPEG filter=lfs diff=lfs merge=lfs -text +*.png filter=lfs diff=lfs merge=lfs -text +*.PNG filter=lfs diff=lfs merge=lfs -text +*.GIF filter=lfs diff=lfs merge=lfs -text +*.TIFF filter=lfs diff=lfs merge=lfs -text +*.SVG filter=lfs diff=lfs merge=lfs -text +*.BMP filter=lfs diff=lfs merge=lfs -text +*.tiff filter=lfs diff=lfs merge=lfs -text +*.ico filter=lfs diff=lfs merge=lfs -text +*.bmp filter=lfs diff=lfs merge=lfs -text +*.gif filter=lfs diff=lfs merge=lfs -text +* !text !filter !merge !diff diff --git a/.github/workflows/actions.yaml b/.github/workflows/actions.yaml new file mode 100644 index 0000000..2ac3fc0 --- /dev/null +++ b/.github/workflows/actions.yaml @@ -0,0 +1,41 @@ +name: GitHub Actions + +on: + pull_request: + branches: + - main + push: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: + - 18 + - 20 + - 22 + steps: + - uses: actions/checkout@v4 + - name: Use Node.js v${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: npm + - run: npm ci + - run: npm run build + # - run: npm test + + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Use Node.js 22 + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + - run: npm ci + - run: npm run check diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..33741a8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# build output +dist/ +.output/ + +# dependencies +node_modules/ + +# logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + + +# environment variables +.env +.env.production + +# macOS-specific files +.DS_Store + +pnpm-lock.yaml + +.astro \ No newline at end of file diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..999db39 --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +# Expose Astro dependencies for `pnpm` users +shamefully-hoist=true \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..76b517c --- /dev/null +++ b/.prettierignore @@ -0,0 +1,4 @@ +dist +node_modules +.github +.changeset \ No newline at end of file diff --git a/.prettierrc.cjs b/.prettierrc.cjs new file mode 100644 index 0000000..752ef12 --- /dev/null +++ b/.prettierrc.cjs @@ -0,0 +1,13 @@ +/** @type {import('prettier').Config} */ +module.exports = { + printWidth: 120, + semi: true, + singleQuote: true, + tabWidth: 2, + trailingComma: 'es5', + useTabs: false, + + plugins: [require.resolve('prettier-plugin-astro')], + + overrides: [{ files: '*.astro', options: { parser: 'astro' } }], +}; diff --git a/.stackblitzrc b/.stackblitzrc new file mode 100644 index 0000000..43798ec --- /dev/null +++ b/.stackblitzrc @@ -0,0 +1,6 @@ +{ + "startCommand": "npm start", + "env": { + "ENABLE_CJS_IMPORTS": true + } +} \ No newline at end of file diff --git a/.vscode/astrowind/config-schema.json b/.vscode/astrowind/config-schema.json new file mode 100644 index 0000000..3297fa3 --- /dev/null +++ b/.vscode/astrowind/config-schema.json @@ -0,0 +1,275 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "site": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "site": { + "type": "string" + }, + "base": { + "type": "string" + }, + "trailingSlash": { + "type": "boolean" + }, + "googleSiteVerificationId": { + "type": "string" + } + }, + "required": ["name", "site", "base", "trailingSlash"], + "additionalProperties": false + }, + "metadata": { + "type": "object", + "properties": { + "title": { + "type": "object", + "properties": { + "default": { + "type": "string" + }, + "template": { + "type": "string" + } + }, + "required": ["default", "template"] + }, + "description": { + "type": "string" + }, + "robots": { + "type": "object", + "properties": { + "index": { + "type": "boolean" + }, + "follow": { + "type": "boolean" + } + }, + "required": ["index", "follow"] + }, + "openGraph": { + "type": "object", + "properties": { + "site_name": { + "type": "string" + }, + "images": { + "type": "array", + "items": [ + { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "width": { + "type": "integer" + }, + "height": { + "type": "integer" + } + }, + "required": ["url", "width", "height"] + } + ] + }, + "type": { + "type": "string" + } + }, + "required": ["site_name", "images", "type"] + }, + "twitter": { + "type": "object", + "properties": { + "handle": { + "type": "string" + }, + "site": { + "type": "string" + }, + "cardType": { + "type": "string" + } + }, + "required": ["handle", "site", "cardType"] + } + }, + "required": ["title", "description", "robots", "openGraph", "twitter"] + }, + "i18n": { + "type": "object", + "properties": { + "language": { + "type": "string" + }, + "textDirection": { + "type": "string" + } + }, + "required": ["language", "textDirection"] + }, + "apps": { + "type": "object", + "properties": { + "blog": { + "type": "object", + "properties": { + "isEnabled": { + "type": "boolean" + }, + "postsPerPage": { + "type": "integer" + }, + "isRelatedPostsEnabled": { + "type": "boolean" + }, + "relatedPostsCount": { + "type": "integer" + }, + "post": { + "type": "object", + "properties": { + "isEnabled": { + "type": "boolean" + }, + "permalink": { + "type": "string" + }, + "robots": { + "type": "object", + "properties": { + "index": { + "type": "boolean" + }, + "follow": { + "type": "boolean" + } + }, + "required": ["index"] + } + }, + "required": ["isEnabled", "permalink", "robots"] + }, + "list": { + "type": "object", + "properties": { + "isEnabled": { + "type": "boolean" + }, + "pathname": { + "type": "string" + }, + "robots": { + "type": "object", + "properties": { + "index": { + "type": "boolean" + }, + "follow": { + "type": "boolean" + } + }, + "required": ["index"] + } + }, + "required": ["isEnabled", "pathname", "robots"] + }, + "category": { + "type": "object", + "properties": { + "isEnabled": { + "type": "boolean" + }, + "pathname": { + "type": "string" + }, + "robots": { + "type": "object", + "properties": { + "index": { + "type": "boolean" + }, + "follow": { + "type": "boolean" + } + }, + "required": ["index"] + } + }, + "required": ["isEnabled", "pathname", "robots"] + }, + "tag": { + "type": "object", + "properties": { + "isEnabled": { + "type": "boolean" + }, + "pathname": { + "type": "string" + }, + "robots": { + "type": "object", + "properties": { + "index": { + "type": "boolean" + }, + "follow": { + "type": "boolean" + } + }, + "required": ["index"] + } + }, + "required": ["isEnabled", "pathname", "robots"] + } + }, + "required": ["isEnabled", "postsPerPage", "post", "list", "category", "tag"] + } + }, + "required": ["blog"] + }, + "analytics": { + "type": "object", + "properties": { + "vendors": { + "type": "object", + "properties": { + "googleAnalytics": { + "type": "object", + "properties": { + "id": { + "type": ["string", "null"] + }, + "partytown": { + "type": "boolean", + "default": true + } + }, + "required": ["id"] + } + }, + "required": ["googleAnalytics"] + } + }, + "required": ["vendors"] + }, + "ui": { + "type": "object", + "properties": { + "theme": { + "type": "string" + } + }, + "required": ["theme"] + } + }, + "required": ["site", "metadata", "i18n", "apps", "analytics", "ui"] +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..ec1bfc0 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,10 @@ +{ + "recommendations": [ + "astro-build.astro-vscode", + "bradlc.vscode-tailwindcss", + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "unifiedjs.vscode-mdx" + ], + "unwantedRecommendations": [] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..d642209 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,11 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "command": "./node_modules/.bin/astro dev", + "name": "Development server", + "request": "launch", + "type": "node-terminal" + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..c400773 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,15 @@ +{ + "css.customData": ["./vscode.tailwind.json"], + "eslint.validate": ["javascript", "javascriptreact", "astro", "typescript", "typescriptreact"], + "files.associations": { + "*.mdx": "markdown" + }, + "prettier.documentSelectors": ["**/*.astro"], + "[astro]": { + "editor.defaultFormatter": "astro-build.astro-vscode" + }, + "yaml.schemas": { + "./.vscode/astrowind/config-schema.json": "/src/config.yaml" + }, + "eslint.useFlatConfig": true +} diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..0aaf61e --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Dawid Rycerz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..f431771 --- /dev/null +++ b/README.md @@ -0,0 +1,95 @@ +# 🚗 CustomWorks Website + +Official website for **CustomWorks** - professional car detailing, wrapping, and tuning services based in Bydgoszcz, Poland. + +## About CustomWorks + +**CustomWorks** specializes in comprehensive car care and customization services, offering: + +- **Car detailing** - professional paint restoration, interior cleaning, and ceramic coatings +- **Paint protection films (PPF)** - invisible protection against scratches and damage +- **Car wrapping** - color change and vinyl wrapping services +- **Visual tuning** - exterior and interior styling modifications +- **Ceramic coatings** - graphene, quartz, and ceramic protection layers +- **Invisible wipers** - revolutionary windshield protection +- **Engine bay cleaning** - professional engine compartment detailing +- **Premium materials** - working with high-quality products and techniques + +### Company Details +- **Owner**: CustomWorks +- **Address**: Bydgoszcz, Miedzyń, Poland +- **Phone**: +48 790-209-770 +- **Website**: https://www.customworks.pl + +### Services +CustomWorks offers a complete range of automotive care services, from basic detailing to advanced protection and customization solutions. + +## Website Technology + +This website is built with: +- **[Astro 5.0](https://astro.build/)** - Modern static site generator +- **[Tailwind CSS](https://tailwindcss.com/)** - Utility-first CSS framework +- **TypeScript** - Type-safe development +- **Responsive design** - Optimized for all devices +- **SEO optimized** - Built-in meta tags, sitemap, and Open Graph support + +## Development + +### Prerequisites +- Node.js 18.17.1 or higher +- npm or yarn + +### Getting Started + +1. **Clone the repository** + ```bash + git clone + cd customworks.com + ``` + +2. **Install dependencies** + ```bash + npm install + ``` + +3. **Start development server** + ```bash + npm run dev + ``` + The site will be available at `http://localhost:4321` + +### Available Commands + +| Command | Description | +|---------|-------------| +| `npm run dev` | Start development server | +| `npm run build` | Build for production | +| `npm run preview` | Preview production build | +| `npm run check` | Check for errors | +| `npm run fix` | Fix linting and formatting issues | + +## Project Structure + +``` +src/ +├── components/ # Reusable UI components +│ ├── widgets/ # Page-specific widgets +│ ├── ui/ # Basic UI components +│ └── common/ # Common components (header, footer, etc.) +├── layouts/ # Page layouts +├── pages/ # Website pages +├── assets/ # Images, styles, and other assets +└── config.yaml # Site configuration +``` + +## Deployment + +This website is configured for deployment on Vercel. The build process creates optimized static files ready for production. + +## License + +This project is proprietary software owned by CustomWorks. All rights reserved. + +--- + +**CustomWorks** - Professional car detailing and customization services in Bydgoszcz and surrounding areas. diff --git a/astro.config.ts b/astro.config.ts new file mode 100644 index 0000000..7afa4c9 --- /dev/null +++ b/astro.config.ts @@ -0,0 +1,94 @@ +import path from 'path'; +import { fileURLToPath } from 'url'; + +import { defineConfig } from 'astro/config'; + +import sitemap from '@astrojs/sitemap'; +import tailwind from '@astrojs/tailwind'; +import mdx from '@astrojs/mdx'; +import partytown from '@astrojs/partytown'; +import icon from 'astro-icon'; +import compress from 'astro-compress'; +import type { AstroIntegration } from 'astro'; + +import astrowind from './vendor/integration'; + +import { readingTimeRemarkPlugin, responsiveTablesRehypePlugin, lazyImagesRehypePlugin } from './src/utils/frontmatter'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +const hasExternalScripts = false; +const whenExternalScripts = (items: (() => AstroIntegration) | (() => AstroIntegration)[] = []) => + hasExternalScripts ? (Array.isArray(items) ? items.map((item) => item()) : [items()]) : []; + +export default defineConfig({ + output: 'static', + + devToolbar: { + enabled: false, + }, + + integrations: [ + tailwind({ + applyBaseStyles: false, + }), + sitemap(), + mdx(), + icon({ + include: { + tabler: ['*'], + 'flat-color-icons': [ + 'template', + 'gallery', + 'approval', + 'document', + 'advertising', + 'currency-exchange', + 'voice-presentation', + 'business-contact', + 'database', + ], + }, + }), + + ...whenExternalScripts(() => + partytown({ + config: { forward: ['dataLayer.push'] }, + }) + ), + + compress({ + CSS: true, + HTML: { + 'html-minifier-terser': { + removeAttributeQuotes: false, + }, + }, + Image: false, + JavaScript: true, + SVG: false, + Logger: 1, + }), + + astrowind({ + config: './src/config.yaml', + }), + ], + + image: { + domains: ['cdn.pixabay.com'], + }, + + markdown: { + remarkPlugins: [readingTimeRemarkPlugin], + rehypePlugins: [responsiveTablesRehypePlugin, lazyImagesRehypePlugin], + }, + + vite: { + resolve: { + alias: { + '~': path.resolve(__dirname, './src'), + }, + }, + }, +}); diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..3961a84 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,59 @@ +import astroEslintParser from 'astro-eslint-parser'; +import eslintPluginAstro from 'eslint-plugin-astro'; +import globals from 'globals'; +import js from '@eslint/js'; +import tseslint from 'typescript-eslint'; +import typescriptParser from '@typescript-eslint/parser'; + +export default [ + js.configs.recommended, + ...eslintPluginAstro.configs['flat/recommended'], + ...tseslint.configs.recommended, + { + languageOptions: { + globals: { + ...globals.browser, + ...globals.node, + }, + }, + }, + { + files: ['**/*.astro'], + languageOptions: { + parser: astroEslintParser, + parserOptions: { + parser: '@typescript-eslint/parser', + extraFileExtensions: ['.astro'], + }, + }, + }, + { + files: ['**/*.{js,jsx,astro}'], + rules: { + 'no-mixed-spaces-and-tabs': ['error', 'smart-tabs'], + }, + }, + { + // Define the configuration for ` diff --git a/src/components/common/BasicScripts.astro b/src/components/common/BasicScripts.astro new file mode 100644 index 0000000..c7290b2 --- /dev/null +++ b/src/components/common/BasicScripts.astro @@ -0,0 +1,255 @@ +--- +import { UI } from 'astrowind:config'; +--- + + + + diff --git a/src/components/common/CommonMeta.astro b/src/components/common/CommonMeta.astro new file mode 100644 index 0000000..aab6dd4 --- /dev/null +++ b/src/components/common/CommonMeta.astro @@ -0,0 +1,8 @@ +--- +import { getAsset } from '~/utils/permalinks'; +--- + + + + + diff --git a/src/components/common/Image.astro b/src/components/common/Image.astro new file mode 100644 index 0000000..d113b68 --- /dev/null +++ b/src/components/common/Image.astro @@ -0,0 +1,61 @@ +--- +import type { HTMLAttributes } from 'astro/types'; +import { findImage } from '~/utils/images'; +import { + getImagesOptimized, + astroAssetsOptimizer, + unpicOptimizer, + isUnpicCompatible, + type ImageProps, +} from '~/utils/images-optimization'; + +type Props = ImageProps; +type ImageType = { + src: string; + attributes: HTMLAttributes<'img'>; +}; + +const props = Astro.props; + +if (props.alt === undefined || props.alt === null) { + throw new Error(); +} + +if (typeof props.width === 'string') { + props.width = parseInt(props.width); +} + +if (typeof props.height === 'string') { + props.height = parseInt(props.height); +} + +if (!props.loading) { + props.loading = 'lazy'; +} + +if (!props.decoding) { + props.decoding = 'async'; +} + +const _image = await findImage(props.src); + +let image: ImageType | undefined = undefined; + +if ( + typeof _image === 'string' && + (_image.startsWith('http://') || _image.startsWith('https://')) && + isUnpicCompatible(_image) +) { + image = await getImagesOptimized(_image, props, unpicOptimizer); +} else if (_image) { + image = await getImagesOptimized(_image, props, astroAssetsOptimizer); +} +--- + +{ + !image ? ( + + ) : ( + + ) +} diff --git a/src/components/common/Metadata.astro b/src/components/common/Metadata.astro new file mode 100644 index 0000000..a4c573e --- /dev/null +++ b/src/components/common/Metadata.astro @@ -0,0 +1,68 @@ +--- +import merge from 'lodash.merge'; +import { AstroSeo } from '@astrolib/seo'; + +import type { Props as AstroSeoProps } from '@astrolib/seo'; + +import { SITE, METADATA, I18N } from 'astrowind:config'; +import type { MetaData } from '~/types'; +import { getCanonical } from '~/utils/permalinks'; + +import { adaptOpenGraphImages } from '~/utils/images'; + +export interface Props extends MetaData { + dontUseTitleTemplate?: boolean; +} + +const { + title, + ignoreTitleTemplate = false, + canonical = String(getCanonical(String(Astro.url.pathname))), + robots = {}, + description, + openGraph = {}, + twitter = {}, +} = Astro.props; + +const seoProps: AstroSeoProps = merge( + { + title: '', + titleTemplate: '%s', + canonical: canonical, + noindex: true, + nofollow: true, + description: undefined, + openGraph: { + url: canonical, + site_name: SITE?.name, + images: [], + locale: I18N?.language || 'en', + type: 'website', + }, + twitter: { + cardType: openGraph?.images?.length ? 'summary_large_image' : 'summary', + }, + }, + { + title: METADATA?.title?.default, + titleTemplate: METADATA?.title?.template, + noindex: typeof METADATA?.robots?.index !== 'undefined' ? !METADATA.robots.index : undefined, + nofollow: typeof METADATA?.robots?.follow !== 'undefined' ? !METADATA.robots.follow : undefined, + description: METADATA?.description, + openGraph: METADATA?.openGraph, + twitter: METADATA?.twitter, + }, + { + title: title, + titleTemplate: ignoreTitleTemplate ? '%s' : undefined, + canonical: canonical, + noindex: typeof robots?.index !== 'undefined' ? !robots.index : undefined, + nofollow: typeof robots?.follow !== 'undefined' ? !robots.follow : undefined, + description: description, + openGraph: { url: canonical, ...openGraph }, + twitter: twitter, + } +); +--- + + diff --git a/src/components/common/SiteVerification.astro b/src/components/common/SiteVerification.astro new file mode 100644 index 0000000..000baad --- /dev/null +++ b/src/components/common/SiteVerification.astro @@ -0,0 +1,5 @@ +--- +import { SITE } from 'astrowind:config'; +--- + +{SITE.googleSiteVerificationId && } diff --git a/src/components/common/SocialShare.astro b/src/components/common/SocialShare.astro new file mode 100644 index 0000000..d035e8f --- /dev/null +++ b/src/components/common/SocialShare.astro @@ -0,0 +1,65 @@ +--- +import { Icon } from 'astro-icon/components'; + +export interface Props { + text: string; + url: string | URL; + class?: string; +} + +const { text, url, class: className = 'inline-block' } = Astro.props; +--- + +
+ Share: + + + + + +
diff --git a/src/components/common/SplitbeeAnalytics.astro b/src/components/common/SplitbeeAnalytics.astro new file mode 100644 index 0000000..66651db --- /dev/null +++ b/src/components/common/SplitbeeAnalytics.astro @@ -0,0 +1,6 @@ +--- +const { doNotTrack = true, noCookieMode = false, url = 'https://cdn.splitbee.io/sb.js' } = Astro.props; +--- + + + diff --git a/src/components/common/ToggleMenu.astro b/src/components/common/ToggleMenu.astro new file mode 100644 index 0000000..2d19b16 --- /dev/null +++ b/src/components/common/ToggleMenu.astro @@ -0,0 +1,29 @@ +--- +export interface Props { + label?: string; + class?: string; +} + +const { + label = 'Toggle Menu', + class: className = 'flex flex-col h-12 w-12 rounded justify-center items-center cursor-pointer group', +} = Astro.props; +--- + + diff --git a/src/components/common/ToggleTheme.astro b/src/components/common/ToggleTheme.astro new file mode 100644 index 0000000..8f3aafb --- /dev/null +++ b/src/components/common/ToggleTheme.astro @@ -0,0 +1,28 @@ +--- +import { Icon } from 'astro-icon/components'; + +import { UI } from 'astrowind:config'; + +export interface Props { + label?: string; + class?: string; + iconClass?: string; + iconName?: string; +} + +const { + label = 'Toggle between Dark and Light mode', + class: + className = 'text-muted dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-700 rounded-lg text-sm p-2.5 inline-flex items-center', + iconClass = 'w-6 h-6', + iconName = 'tabler:sun', +} = Astro.props; +--- + +{ + !(UI.theme && UI.theme.endsWith(':only')) && ( + + ) +} diff --git a/src/components/ui/Background.astro b/src/components/ui/Background.astro new file mode 100644 index 0000000..f220487 --- /dev/null +++ b/src/components/ui/Background.astro @@ -0,0 +1,11 @@ +--- +export interface Props { + isDark?: boolean; +} + +const { isDark = false } = Astro.props; +--- + +
+ +
diff --git a/src/components/ui/Button.astro b/src/components/ui/Button.astro new file mode 100644 index 0000000..d3c2398 --- /dev/null +++ b/src/components/ui/Button.astro @@ -0,0 +1,40 @@ +--- +import { Icon } from 'astro-icon/components'; +import { twMerge } from 'tailwind-merge'; +import type { CallToAction as Props } from '~/types'; + +const { + variant = 'secondary', + target, + text = Astro.slots.render('default'), + icon = '', + class: className = '', + type, + ...rest +} = Astro.props; + +const variants = { + primary: 'btn-primary', + secondary: 'btn-secondary', + tertiary: 'btn btn-tertiary', + link: 'cursor-pointer hover:text-primary', +}; +--- + +{ + type === 'button' || type === 'submit' || type === 'reset' ? ( + + ) : ( + + + {icon && } + + ) +} diff --git a/src/components/ui/DListItem.astro b/src/components/ui/DListItem.astro new file mode 100644 index 0000000..36d4072 --- /dev/null +++ b/src/components/ui/DListItem.astro @@ -0,0 +1,22 @@ +--- +// component: DListItem +// +// Mimics the html 'dl' (description list) +// +// The 'dt' item is the item 'term' and is inserted into an 'h6' tag. +// Caller needs to style the 'h6' tag appropriately. +// +// You can put pretty much any content you want between the open and +// closing tags - it's simply contained in an enclosing div with a +// margin left. No need for 'dd' items. +// +const { dt } = Astro.props; +interface Props { + dt: string; +} + +const content: string = await Astro.slots.render('default'); +--- + +
+
diff --git a/src/components/ui/Form.astro b/src/components/ui/Form.astro new file mode 100644 index 0000000..276b39f --- /dev/null +++ b/src/components/ui/Form.astro @@ -0,0 +1,87 @@ +--- +import type { Form as Props } from '~/types'; +import Button from '~/components/ui/Button.astro'; + +const { inputs, textarea, disclaimer, button = 'Contact us', description = '' } = Astro.props; +--- + +
+ { + inputs && + inputs.map( + ({ type = 'text', name, label = '', autocomplete = 'on', placeholder = '' }) => + name && ( +
+ {label && ( + + )} + +
+ ) + ) + } + + { + textarea && ( +
+ +