├── .dockerignore ├── .editorconfig ├── .github └── workflows │ └── actions.yaml ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc.cjs ├── .stackblitzrc ├── .vscode ├── astrowind │ └── config-schema.json ├── extensions.json ├── launch.json └── settings.json ├── Dockerfile ├── LICENSE.md ├── README.md ├── astro.config.ts ├── docker-compose.yml ├── eslint.config.js ├── netlify.toml ├── nginx └── nginx.conf ├── package-lock.json ├── package.json ├── public ├── _headers ├── decapcms │ ├── config.yml │ └── index.html └── robots.txt ├── sandbox.config.json ├── src ├── assets │ ├── favicons │ │ ├── apple-touch-icon.png │ │ ├── favicon.ico │ │ └── favicon.svg │ ├── images │ │ ├── app-store.png │ │ ├── default.png │ │ ├── google-play.png │ │ └── hero-image.png │ └── styles │ │ └── tailwind.css ├── components │ ├── CustomStyles.astro │ ├── Favicons.astro │ ├── Logo.astro │ ├── blog │ │ ├── Grid.astro │ │ ├── GridItem.astro │ │ ├── Headline.astro │ │ ├── List.astro │ │ ├── ListItem.astro │ │ ├── Pagination.astro │ │ ├── RelatedPosts.astro │ │ ├── SinglePost.astro │ │ ├── Tags.astro │ │ └── ToBlogLink.astro │ ├── common │ │ ├── Analytics.astro │ │ ├── ApplyColorMode.astro │ │ ├── BasicScripts.astro │ │ ├── CommonMeta.astro │ │ ├── Image.astro │ │ ├── Metadata.astro │ │ ├── SiteVerification.astro │ │ ├── SocialShare.astro │ │ ├── SplitbeeAnalytics.astro │ │ ├── ToggleMenu.astro │ │ └── ToggleTheme.astro │ ├── ui │ │ ├── Background.astro │ │ ├── Button.astro │ │ ├── DListItem.astro │ │ ├── Form.astro │ │ ├── Headline.astro │ │ ├── ItemGrid.astro │ │ ├── ItemGrid2.astro │ │ ├── Timeline.astro │ │ └── WidgetWrapper.astro │ └── widgets │ │ ├── Announcement.astro │ │ ├── BlogHighlightedPosts.astro │ │ ├── BlogLatestPosts.astro │ │ ├── Brands.astro │ │ ├── CallToAction.astro │ │ ├── Contact.astro │ │ ├── Content.astro │ │ ├── FAQs.astro │ │ ├── Features.astro │ │ ├── Features2.astro │ │ ├── Features3.astro │ │ ├── Footer.astro │ │ ├── Header.astro │ │ ├── Hero.astro │ │ ├── Hero2.astro │ │ ├── HeroText.astro │ │ ├── Note.astro │ │ ├── Pricing.astro │ │ ├── Stats.astro │ │ ├── Steps.astro │ │ ├── Steps2.astro │ │ └── Testimonials.astro ├── config.yaml ├── content │ └── config.ts ├── data │ └── post │ │ ├── astrowind-template-in-depth.mdx │ │ ├── get-started-website-with-astro-tailwind-css.md │ │ ├── how-to-customize-astrowind-to-your-brand.md │ │ ├── landing.md │ │ ├── markdown-elements-demo-post.mdx │ │ └── useful-resources-to-create-websites.md ├── env.d.ts ├── layouts │ ├── LandingLayout.astro │ ├── Layout.astro │ ├── MarkdownLayout.astro │ └── PageLayout.astro ├── navigation.ts ├── pages │ ├── 404.astro │ ├── [...blog] │ │ ├── [...page].astro │ │ ├── [category] │ │ │ └── [...page].astro │ │ ├── [tag] │ │ │ └── [...page].astro │ │ └── index.astro │ ├── about.astro │ ├── contact.astro │ ├── homes │ │ ├── mobile-app.astro │ │ ├── personal.astro │ │ ├── saas.astro │ │ └── startup.astro │ ├── index.astro │ ├── landing │ │ ├── click-through.astro │ │ ├── lead-generation.astro │ │ ├── pre-launch.astro │ │ ├── product.astro │ │ ├── sales.astro │ │ └── subscription.astro │ ├── pricing.astro │ ├── privacy.md │ ├── rss.xml.ts │ ├── services.astro │ └── terms.md ├── types.d.ts └── utils │ ├── blog.ts │ ├── directories.ts │ ├── frontmatter.ts │ ├── images-optimization.ts │ ├── images.ts │ ├── permalinks.ts │ └── utils.ts ├── tailwind.config.js ├── tsconfig.json ├── vendor ├── README.md └── integration │ ├── index.ts │ ├── types.d.ts │ └── utils │ ├── configBuilder.ts │ └── loadConfig.ts ├── vercel.json └── vscode.tailwind.json /.dockerignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | node_modules/ 3 | .vscode/ 4 | .github/ 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | end_of_line = lf 9 | indent_size = 2 10 | indent_style = space 11 | insert_final_newline = true 12 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.github/workflows/actions.yaml: -------------------------------------------------------------------------------- 1 | name: GitHub Actions 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | push: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | node-version: 17 | - 18 18 | - 20 19 | - 22 20 | steps: 21 | - uses: actions/checkout@v4 22 | - name: Use Node.js v${{ matrix.node-version }} 23 | uses: actions/setup-node@v4 24 | with: 25 | node-version: ${{ matrix.node-version }} 26 | cache: npm 27 | - run: npm ci 28 | - run: npm run build 29 | # - run: npm test 30 | 31 | check: 32 | runs-on: ubuntu-latest 33 | steps: 34 | - uses: actions/checkout@v4 35 | - name: Use Node.js 22 36 | uses: actions/setup-node@v4 37 | with: 38 | node-version: 22 39 | cache: npm 40 | - run: npm ci 41 | - run: npm run check 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | .output/ 4 | 5 | # dependencies 6 | node_modules/ 7 | 8 | # logs 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | pnpm-debug.log* 13 | 14 | 15 | # environment variables 16 | .env 17 | .env.production 18 | 19 | # macOS-specific files 20 | .DS_Store 21 | 22 | pnpm-lock.yaml 23 | 24 | .astro -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | # Expose Astro dependencies for `pnpm` users 2 | shamefully-hoist=true -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | .github 4 | .changeset -------------------------------------------------------------------------------- /.prettierrc.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import('prettier').Config} */ 2 | module.exports = { 3 | printWidth: 120, 4 | semi: true, 5 | singleQuote: true, 6 | tabWidth: 2, 7 | trailingComma: 'es5', 8 | useTabs: false, 9 | 10 | plugins: [require.resolve('prettier-plugin-astro')], 11 | 12 | overrides: [{ files: '*.astro', options: { parser: 'astro' } }], 13 | }; 14 | -------------------------------------------------------------------------------- /.stackblitzrc: -------------------------------------------------------------------------------- 1 | { 2 | "startCommand": "npm start", 3 | "env": { 4 | "ENABLE_CJS_IMPORTS": true 5 | } 6 | } -------------------------------------------------------------------------------- /.vscode/astrowind/config-schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "type": "object", 4 | "properties": { 5 | "site": { 6 | "type": "object", 7 | "properties": { 8 | "name": { 9 | "type": "string" 10 | }, 11 | "site": { 12 | "type": "string" 13 | }, 14 | "base": { 15 | "type": "string" 16 | }, 17 | "trailingSlash": { 18 | "type": "boolean" 19 | }, 20 | "googleSiteVerificationId": { 21 | "type": "string" 22 | } 23 | }, 24 | "required": ["name", "site", "base", "trailingSlash"], 25 | "additionalProperties": false 26 | }, 27 | "metadata": { 28 | "type": "object", 29 | "properties": { 30 | "title": { 31 | "type": "object", 32 | "properties": { 33 | "default": { 34 | "type": "string" 35 | }, 36 | "template": { 37 | "type": "string" 38 | } 39 | }, 40 | "required": ["default", "template"] 41 | }, 42 | "description": { 43 | "type": "string" 44 | }, 45 | "robots": { 46 | "type": "object", 47 | "properties": { 48 | "index": { 49 | "type": "boolean" 50 | }, 51 | "follow": { 52 | "type": "boolean" 53 | } 54 | }, 55 | "required": ["index", "follow"] 56 | }, 57 | "openGraph": { 58 | "type": "object", 59 | "properties": { 60 | "site_name": { 61 | "type": "string" 62 | }, 63 | "images": { 64 | "type": "array", 65 | "items": [ 66 | { 67 | "type": "object", 68 | "properties": { 69 | "url": { 70 | "type": "string" 71 | }, 72 | "width": { 73 | "type": "integer" 74 | }, 75 | "height": { 76 | "type": "integer" 77 | } 78 | }, 79 | "required": ["url", "width", "height"] 80 | } 81 | ] 82 | }, 83 | "type": { 84 | "type": "string" 85 | } 86 | }, 87 | "required": ["site_name", "images", "type"] 88 | }, 89 | "twitter": { 90 | "type": "object", 91 | "properties": { 92 | "handle": { 93 | "type": "string" 94 | }, 95 | "site": { 96 | "type": "string" 97 | }, 98 | "cardType": { 99 | "type": "string" 100 | } 101 | }, 102 | "required": ["handle", "site", "cardType"] 103 | } 104 | }, 105 | "required": ["title", "description", "robots", "openGraph", "twitter"] 106 | }, 107 | "i18n": { 108 | "type": "object", 109 | "properties": { 110 | "language": { 111 | "type": "string" 112 | }, 113 | "textDirection": { 114 | "type": "string" 115 | } 116 | }, 117 | "required": ["language", "textDirection"] 118 | }, 119 | "apps": { 120 | "type": "object", 121 | "properties": { 122 | "blog": { 123 | "type": "object", 124 | "properties": { 125 | "isEnabled": { 126 | "type": "boolean" 127 | }, 128 | "postsPerPage": { 129 | "type": "integer" 130 | }, 131 | "isRelatedPostsEnabled": { 132 | "type": "boolean" 133 | }, 134 | "relatedPostsCount": { 135 | "type": "integer" 136 | }, 137 | "post": { 138 | "type": "object", 139 | "properties": { 140 | "isEnabled": { 141 | "type": "boolean" 142 | }, 143 | "permalink": { 144 | "type": "string" 145 | }, 146 | "robots": { 147 | "type": "object", 148 | "properties": { 149 | "index": { 150 | "type": "boolean" 151 | }, 152 | "follow": { 153 | "type": "boolean" 154 | } 155 | }, 156 | "required": ["index"] 157 | } 158 | }, 159 | "required": ["isEnabled", "permalink", "robots"] 160 | }, 161 | "list": { 162 | "type": "object", 163 | "properties": { 164 | "isEnabled": { 165 | "type": "boolean" 166 | }, 167 | "pathname": { 168 | "type": "string" 169 | }, 170 | "robots": { 171 | "type": "object", 172 | "properties": { 173 | "index": { 174 | "type": "boolean" 175 | }, 176 | "follow": { 177 | "type": "boolean" 178 | } 179 | }, 180 | "required": ["index"] 181 | } 182 | }, 183 | "required": ["isEnabled", "pathname", "robots"] 184 | }, 185 | "category": { 186 | "type": "object", 187 | "properties": { 188 | "isEnabled": { 189 | "type": "boolean" 190 | }, 191 | "pathname": { 192 | "type": "string" 193 | }, 194 | "robots": { 195 | "type": "object", 196 | "properties": { 197 | "index": { 198 | "type": "boolean" 199 | }, 200 | "follow": { 201 | "type": "boolean" 202 | } 203 | }, 204 | "required": ["index"] 205 | } 206 | }, 207 | "required": ["isEnabled", "pathname", "robots"] 208 | }, 209 | "tag": { 210 | "type": "object", 211 | "properties": { 212 | "isEnabled": { 213 | "type": "boolean" 214 | }, 215 | "pathname": { 216 | "type": "string" 217 | }, 218 | "robots": { 219 | "type": "object", 220 | "properties": { 221 | "index": { 222 | "type": "boolean" 223 | }, 224 | "follow": { 225 | "type": "boolean" 226 | } 227 | }, 228 | "required": ["index"] 229 | } 230 | }, 231 | "required": ["isEnabled", "pathname", "robots"] 232 | } 233 | }, 234 | "required": ["isEnabled", "postsPerPage", "post", "list", "category", "tag"] 235 | } 236 | }, 237 | "required": ["blog"] 238 | }, 239 | "analytics": { 240 | "type": "object", 241 | "properties": { 242 | "vendors": { 243 | "type": "object", 244 | "properties": { 245 | "googleAnalytics": { 246 | "type": "object", 247 | "properties": { 248 | "id": { 249 | "type": ["string", "null"] 250 | }, 251 | "partytown": { 252 | "type": "boolean", 253 | "default": true 254 | } 255 | }, 256 | "required": ["id"] 257 | } 258 | }, 259 | "required": ["googleAnalytics"] 260 | } 261 | }, 262 | "required": ["vendors"] 263 | }, 264 | "ui": { 265 | "type": "object", 266 | "properties": { 267 | "theme": { 268 | "type": "string" 269 | } 270 | }, 271 | "required": ["theme"] 272 | } 273 | }, 274 | "required": ["site", "metadata", "i18n", "apps", "analytics", "ui"] 275 | } 276 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "astro-build.astro-vscode", 4 | "bradlc.vscode-tailwindcss", 5 | "dbaeumer.vscode-eslint", 6 | "esbenp.prettier-vscode", 7 | "unifiedjs.vscode-mdx" 8 | ], 9 | "unwantedRecommendations": [] 10 | } 11 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "command": "./node_modules/.bin/astro dev", 6 | "name": "Development server", 7 | "request": "launch", 8 | "type": "node-terminal" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "css.customData": ["./vscode.tailwind.json"], 3 | "eslint.validate": ["javascript", "javascriptreact", "astro", "typescript", "typescriptreact"], 4 | "files.associations": { 5 | "*.mdx": "markdown" 6 | }, 7 | "prettier.documentSelectors": ["**/*.astro"], 8 | "[astro]": { 9 | "editor.defaultFormatter": "astro-build.astro-vscode" 10 | }, 11 | "yaml.schemas": { 12 | "./.vscode/astrowind/config-schema.json": "/src/config.yaml" 13 | }, 14 | "eslint.useFlatConfig": true 15 | } 16 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:lts AS base 2 | WORKDIR /app 3 | 4 | FROM base AS deps 5 | COPY package*.json ./ 6 | RUN npm install 7 | 8 | FROM base AS build 9 | COPY --from=deps /app/node_modules ./node_modules 10 | COPY . . 11 | RUN npm run build 12 | 13 | FROM nginx:stable-alpine AS deploy 14 | COPY --from=build /app/dist /usr/share/nginx/html 15 | COPY ./nginx/nginx.conf /etc/nginx/nginx.conf 16 | 17 | EXPOSE 8080 18 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 onWidget 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /astro.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { fileURLToPath } from 'url'; 3 | 4 | import { defineConfig } from 'astro/config'; 5 | 6 | import sitemap from '@astrojs/sitemap'; 7 | import tailwind from '@astrojs/tailwind'; 8 | import mdx from '@astrojs/mdx'; 9 | import partytown from '@astrojs/partytown'; 10 | import icon from 'astro-icon'; 11 | import compress from 'astro-compress'; 12 | import type { AstroIntegration } from 'astro'; 13 | 14 | import astrowind from './vendor/integration'; 15 | 16 | import { readingTimeRemarkPlugin, responsiveTablesRehypePlugin, lazyImagesRehypePlugin } from './src/utils/frontmatter'; 17 | 18 | const __dirname = path.dirname(fileURLToPath(import.meta.url)); 19 | 20 | const hasExternalScripts = false; 21 | const whenExternalScripts = (items: (() => AstroIntegration) | (() => AstroIntegration)[] = []) => 22 | hasExternalScripts ? (Array.isArray(items) ? items.map((item) => item()) : [items()]) : []; 23 | 24 | export default defineConfig({ 25 | output: 'static', 26 | 27 | integrations: [ 28 | tailwind({ 29 | applyBaseStyles: false, 30 | }), 31 | sitemap(), 32 | mdx(), 33 | icon({ 34 | include: { 35 | tabler: ['*'], 36 | 'flat-color-icons': [ 37 | 'template', 38 | 'gallery', 39 | 'approval', 40 | 'document', 41 | 'advertising', 42 | 'currency-exchange', 43 | 'voice-presentation', 44 | 'business-contact', 45 | 'database', 46 | ], 47 | }, 48 | }), 49 | 50 | ...whenExternalScripts(() => 51 | partytown({ 52 | config: { forward: ['dataLayer.push'] }, 53 | }) 54 | ), 55 | 56 | compress({ 57 | CSS: true, 58 | HTML: { 59 | 'html-minifier-terser': { 60 | removeAttributeQuotes: false, 61 | }, 62 | }, 63 | Image: false, 64 | JavaScript: true, 65 | SVG: false, 66 | Logger: 1, 67 | }), 68 | 69 | astrowind({ 70 | config: './src/config.yaml', 71 | }), 72 | ], 73 | 74 | image: { 75 | domains: ['cdn.pixabay.com'], 76 | }, 77 | 78 | markdown: { 79 | remarkPlugins: [readingTimeRemarkPlugin], 80 | rehypePlugins: [responsiveTablesRehypePlugin, lazyImagesRehypePlugin], 81 | }, 82 | 83 | vite: { 84 | resolve: { 85 | alias: { 86 | '~': path.resolve(__dirname, './src'), 87 | }, 88 | }, 89 | }, 90 | }); 91 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | astrowind: 3 | build: . 4 | container_name: astrowind 5 | ports: 6 | - 8080:8080 7 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import astroEslintParser from 'astro-eslint-parser'; 2 | import eslintPluginAstro from 'eslint-plugin-astro'; 3 | import globals from 'globals'; 4 | import js from '@eslint/js'; 5 | import tseslint from 'typescript-eslint'; 6 | import typescriptParser from '@typescript-eslint/parser'; 7 | 8 | export default [ 9 | js.configs.recommended, 10 | ...eslintPluginAstro.configs['flat/recommended'], 11 | ...tseslint.configs.recommended, 12 | { 13 | languageOptions: { 14 | globals: { 15 | ...globals.browser, 16 | ...globals.node, 17 | }, 18 | }, 19 | }, 20 | { 21 | files: ['**/*.astro'], 22 | languageOptions: { 23 | parser: astroEslintParser, 24 | parserOptions: { 25 | parser: '@typescript-eslint/parser', 26 | extraFileExtensions: ['.astro'], 27 | }, 28 | }, 29 | }, 30 | { 31 | files: ['**/*.{js,jsx,astro}'], 32 | rules: { 33 | 'no-mixed-spaces-and-tabs': ['error', 'smart-tabs'], 34 | }, 35 | }, 36 | { 37 | // Define the configuration for ` 9 | 10 |
11 | 12 | 13 | 14 |