diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..f78d7f1 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,17 @@ +.DS_Store +.astro +.git +.github +.vscode + +node_modules +dist + +.env +.env.* + +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +README.md diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml new file mode 100644 index 0000000..e7a6538 --- /dev/null +++ b/.github/workflows/deploy.yaml @@ -0,0 +1,25 @@ +name: Build & Deploy (prod) + +"on": + push: + branches: + - main + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Build, push, bump infra + uses: https://git.rlugo.dev/resuely/ci-actions/.github/actions/build-push-bump@main + with: + registry: git.rlugo.dev + image: git.rlugo.dev/resuely/landing + infraRepo: git.rlugo.dev/resuely/infra.git + stackEnvPath: stacks/resuely/prod/stack.env + stackEnvKey: LANDING_IMAGE_TAG + registryUsername: ${{ secrets.REGISTRY_USERNAME }} + registryToken: ${{ secrets.REGISTRY_TOKEN }} + infraPushToken: ${{ secrets.INFRA_PUSH_TOKEN }} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..dd5c3d3 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,51 @@ +# ----- Build deps ----- +FROM node:22-alpine AS deps + +WORKDIR /app + +COPY package.json package-lock.json starwind-*.tgz ./ +RUN npm ci + +# ----- Build ----- +FROM node:22-alpine AS build + +WORKDIR /app + +COPY --from=deps /app/node_modules ./node_modules +COPY package.json package-lock.json starwind-*.tgz ./ +COPY astro.config.* ./ +COPY tsconfig.json ./ +COPY src ./src +COPY public ./public + +ENV NODE_ENV=production +RUN npm run build + +# ----- Production deps ----- +FROM node:22-alpine AS prod-deps + +WORKDIR /app + +COPY package.json package-lock.json starwind-*.tgz ./ +RUN npm ci --omit=dev + +# ----- Runtime ----- +FROM node:22-alpine AS runtime + +WORKDIR /app + +ENV NODE_ENV=production +ENV HOST=0.0.0.0 +ENV PORT=4321 + +COPY --from=prod-deps /app/node_modules ./node_modules +COPY --from=build /app/dist ./dist + +USER node + +EXPOSE 4321 + +HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \ + CMD node -e "require('http').get({host:'127.0.0.1',port:process.env.PORT||4321,path:'/api/healthz'},(r)=>process.exit((r.statusCode||500)<500?0:1)).on('error',()=>process.exit(1))" + +CMD ["node", "./dist/server/entry.mjs"] diff --git a/astro.config.mjs b/astro.config.mjs index 6b749b9..0e2815e 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -1,12 +1,18 @@ -import tailwindcss from "@tailwindcss/vite"; // @ts-check -import { defineConfig } from 'astro/config'; +import { defineConfig } from "astro/config"; +import node from "@astrojs/node"; +import tailwindcss from "@tailwindcss/vite"; -import tailwindcss from '@tailwindcss/vite'; - -// https://astro.build/config export default defineConfig({ - vite: { - plugins: [tailwindcss()] - } -}); \ No newline at end of file + site: "https://resuely.com", + output: "server", + adapter: node({ mode: "standalone" }), + vite: { + plugins: [tailwindcss()], + resolve: { + alias: { + "@": decodeURI(new URL("./src", import.meta.url).pathname), + }, + }, + }, +}); diff --git a/package-lock.json b/package-lock.json index 377119e..90d7ecd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,10 +8,17 @@ "name": "landing", "version": "0.0.1", "dependencies": { + "@astrojs/node": "^9.5.2", + "@astrojs/ts-plugin": "^1.10.6", + "@starwind-ui/core": "^1.15.2", "@tabler/icons": "^3.36.1", "@tailwindcss/forms": "^0.5.11", "@tailwindcss/vite": "^4.1.18", "astro": "^5.16.9", + "embla-carousel": "^8.6.0", + "embla-carousel-autoplay": "^8.6.0", + "motion": "^12.26.2", + "starwind": "file:starwind-1.15.2.tgz", "tailwind-merge": "^3.4.0", "tailwind-variants": "^3.2.2", "tailwindcss": "^4.1.18", @@ -59,6 +66,20 @@ "vfile": "^6.0.3" } }, + "node_modules/@astrojs/node": { + "version": "9.5.2", + "resolved": "https://registry.npmjs.org/@astrojs/node/-/node-9.5.2.tgz", + "integrity": "sha512-85/x+FRwbNGDip1TzSGMiak31/6LvBhA8auqd9lLoHaM5XElk+uIfIr3KjJqucDojE0PtiLk1lMSwD9gd3YlGg==", + "license": "MIT", + "dependencies": { + "@astrojs/internal-helpers": "0.7.5", + "send": "^1.2.1", + "server-destroy": "^1.0.1" + }, + "peerDependencies": { + "astro": "^5.14.3" + } + }, "node_modules/@astrojs/prism": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-3.3.0.tgz", @@ -89,6 +110,30 @@ "node": "18.20.8 || ^20.3.0 || >=22.0.0" } }, + "node_modules/@astrojs/ts-plugin": { + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/@astrojs/ts-plugin/-/ts-plugin-1.10.6.tgz", + "integrity": "sha512-Ke5CNwxn/ozsh6THJKuayUlBToa3uiPDi2oSwcXmTdeiJ0PGr+UkdQJf9hdMgBjbIka9fhnSn3UhYamfNfJ73A==", + "license": "MIT", + "dependencies": { + "@astrojs/compiler": "^2.10.3", + "@astrojs/yaml2ts": "^0.2.2", + "@jridgewell/sourcemap-codec": "^1.4.15", + "@volar/language-core": "~2.4.23", + "@volar/typescript": "~2.4.23", + "semver": "^7.3.8", + "vscode-languageserver-textdocument": "^1.0.11" + } + }, + "node_modules/@astrojs/yaml2ts": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@astrojs/yaml2ts/-/yaml2ts-0.2.2.tgz", + "integrity": "sha512-GOfvSr5Nqy2z5XiwqTouBBpy5FyI6DEe+/g/Mk5am9SjILN1S5fOEvYK0GuWHg98yS/dobP4m8qyqw/URW35fQ==", + "license": "MIT", + "dependencies": { + "yaml": "^2.5.0" + } + }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", @@ -147,6 +192,27 @@ "node": ">=18" } }, + "node_modules/@clack/core": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@clack/core/-/core-0.5.0.tgz", + "integrity": "sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow==", + "license": "MIT", + "dependencies": { + "picocolors": "^1.0.0", + "sisteransi": "^1.0.5" + } + }, + "node_modules/@clack/prompts": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-0.11.0.tgz", + "integrity": "sha512-pMN5FcrEw9hUkZA4f+zLlzivQSeQf5dRGJjSUbvVYDLvpKCdQx5OaknvKzgbtXOizhP+SJJJjqEbOe55uKKfAw==", + "license": "MIT", + "dependencies": { + "@clack/core": "0.5.0", + "picocolors": "^1.0.0", + "sisteransi": "^1.0.5" + } + }, "node_modules/@emnapi/runtime": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", @@ -1443,6 +1509,12 @@ "win32" ] }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "license": "MIT" + }, "node_modules/@shikijs/core": { "version": "3.21.0", "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.21.0.tgz", @@ -1510,6 +1582,24 @@ "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", "license": "MIT" }, + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@starwind-ui/core": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/@starwind-ui/core/-/core-1.15.2.tgz", + "integrity": "sha512-4D4+/AdcSfaujNXNKq0rsCm/3IF0f8W63Qa3z4REn16S0LYRJK/w2qPSKiK8PprpyWyqHuYBp/wtCxcFjoUQ7Q==", + "license": "MIT" + }, "node_modules/@tabler/icons": { "version": "3.36.1", "resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-3.36.1.tgz", @@ -1849,6 +1939,32 @@ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", "license": "ISC" }, + "node_modules/@volar/language-core": { + "version": "2.4.27", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.27.tgz", + "integrity": "sha512-DjmjBWZ4tJKxfNC1F6HyYERNHPYS7L7OPFyCrestykNdUZMFYzI9WTyvwPcaNaHlrEUwESHYsfEw3isInncZxQ==", + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.27" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.27", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.27.tgz", + "integrity": "sha512-ynlcBReMgOZj2i6po+qVswtDUeeBRCTgDurjMGShbm8WYZgJ0PA4RmtebBJ0BCYol1qPv3GQF6jK7C9qoVc7lg==", + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.27", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.27.tgz", + "integrity": "sha512-eWaYCcl/uAPInSK2Lze6IqVWaBu/itVqR5InXcHXFyles4zO++Mglt3oxdgj75BDcv1Knr9Y93nowS8U3wqhxg==", + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.27", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -2283,6 +2399,20 @@ "integrity": "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==", "license": "MIT" }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/crossws": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/crossws/-/crossws-0.3.5.tgz", @@ -2560,6 +2690,21 @@ "node": ">=4" } }, + "node_modules/embla-carousel": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz", + "integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==", + "license": "MIT" + }, + "node_modules/embla-carousel-autoplay": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/embla-carousel-autoplay/-/embla-carousel-autoplay-8.6.0.tgz", + "integrity": "sha512-OBu5G3nwaSXkZCo1A6LTaFMZ8EpkYbwIaH+bPqdBnDGQ2fh4+NbzjXjs2SktoPNKCtflfVMc75njaDHOYXcrsA==", + "license": "MIT", + "peerDependencies": { + "embla-carousel": "8.6.0" + } + }, "node_modules/emoji-regex": { "version": "10.6.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", @@ -2665,6 +2810,32 @@ "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", "license": "MIT" }, + "node_modules/execa": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", + "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.6", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.1", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.2.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": "^18.19.0 || >=20.5.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -2688,6 +2859,21 @@ } } }, + "node_modules/figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/flattie": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/flattie/-/flattie-1.1.1.tgz", @@ -2718,6 +2904,47 @@ "node": ">=24.12.0" } }, + "node_modules/framer-motion": { + "version": "12.26.2", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.26.2.tgz", + "integrity": "sha512-lflOQEdjquUi9sCg5Y1LrsZDlsjrHw7m0T9Yedvnk7Bnhqfkc89/Uha10J3CFhkL+TCZVCRw9eUGyM/lyYhXQA==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.26.2", + "motion-utils": "^12.24.10", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/fs-extra": { + "version": "11.3.3", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.3.tgz", + "integrity": "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -2744,6 +2971,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "license": "MIT", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/github-slugger": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", @@ -2972,6 +3215,15 @@ "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", "license": "BSD-2-Clause" }, + "node_modules/human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, "node_modules/import-meta-resolve": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", @@ -3045,6 +3297,30 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-wsl": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", @@ -3060,6 +3336,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, "node_modules/jiti": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", @@ -3081,6 +3363,18 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -4191,6 +4485,47 @@ "mini-svg-data-uri": "cli.js" } }, + "node_modules/motion": { + "version": "12.26.2", + "resolved": "https://registry.npmjs.org/motion/-/motion-12.26.2.tgz", + "integrity": "sha512-2Q6g0zK1gUJKhGT742DAe42LgietcdiJ3L3OcYAHCQaC1UkLnn6aC8S/obe4CxYTLAgid2asS1QdQ/blYfo5dw==", + "license": "MIT", + "dependencies": { + "framer-motion": "^12.26.2", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/motion-dom": { + "version": "12.26.2", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.26.2.tgz", + "integrity": "sha512-KLMT1BroY8oKNeliA3JMNJ+nbCIsTKg6hJpDb4jtRAJ7nCKnnpg/LTq/NGqG90Limitz3kdAnAVXecdFVGlWTw==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.24.10" + } + }, + "node_modules/motion-utils": { + "version": "12.24.10", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.24.10.tgz", + "integrity": "sha512-x5TFgkCIP4pPsRLpKoI86jv/q8t8FQOiM/0E8QKBzfMozWHfkKap2gA1hOki+B5g3IsBNpxbUnfOum1+dgvYww==", + "license": "MIT" + }, "node_modules/mrmime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", @@ -4267,6 +4602,34 @@ "node": ">=0.10.0" } }, + "node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", @@ -4380,6 +4743,18 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/parse5": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", @@ -4392,6 +4767,21 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "license": "MIT" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/piccolore": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/piccolore/-/piccolore-0.1.3.tgz", @@ -4444,6 +4834,21 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/pretty-ms": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", + "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==", + "license": "MIT", + "dependencies": { + "parse-ms": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/prismjs": { "version": "1.30.0", "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", @@ -4727,7 +5132,6 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.1.tgz", "integrity": "sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==", "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -4833,6 +5237,27 @@ "@img/sharp-win32-x64": "0.34.5" } }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/shiki": { "version": "3.21.0", "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.21.0.tgz", @@ -4849,6 +5274,18 @@ "@types/hast": "^3.0.4" } }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -4886,6 +5323,37 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/starwind": { + "version": "1.15.2", + "resolved": "file:starwind-1.15.2.tgz", + "integrity": "sha512-Grr5oaW3PGkKhQGA10pqlcb40bhk6b5HJOIY+jP9hGFOp+fcEqmgRiSRFqCf8stXNQMC4vl9+Yzp6YM0Yz92qg==", + "license": "MIT", + "dependencies": { + "@clack/prompts": "^0.11.0", + "@starwind-ui/core": "1.15.2", + "chalk": "^5.6.2", + "commander": "^14.0.2", + "execa": "^9.6.0", + "fs-extra": "^11.3.2", + "semver": "^7.7.3", + "zod": "^3.25.74" + }, + "bin": { + "starwind": "dist/index.js" + }, + "engines": { + "node": "^20.6.0 || >=22.0.0" + } + }, + "node_modules/starwind/node_modules/commander": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, "node_modules/string-width": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", @@ -4932,6 +5400,18 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/strip-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/svgo": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.0.tgz", @@ -4962,7 +5442,6 @@ "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz", "integrity": "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==", "license": "MIT", - "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/dcastil" @@ -4991,8 +5470,7 @@ "version": "4.1.18", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/tapable": { "version": "2.3.0", @@ -5082,8 +5560,7 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD", - "optional": true + "license": "0BSD" }, "node_modules/tw-animate-css": { "version": "1.4.0", @@ -5138,6 +5615,18 @@ "integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==", "license": "MIT" }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/unified": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", @@ -5291,6 +5780,15 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/unstorage": { "version": "1.17.4", "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.17.4.tgz", @@ -5434,7 +5932,6 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -5523,6 +6020,18 @@ } } }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", + "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", + "license": "MIT" + }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "license": "MIT" + }, "node_modules/web-namespaces": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", @@ -5533,6 +6042,21 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/which-pm-runs": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.1.0.tgz", @@ -5580,6 +6104,21 @@ "integrity": "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==", "license": "MIT" }, + "node_modules/yaml": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, "node_modules/yargs-parser": { "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", @@ -5633,7 +6172,6 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } @@ -5665,6 +6203,182 @@ "type": "github", "url": "https://github.com/sponsors/wooorm" } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/server-destroy": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/server-destroy/-/server-destroy-1.0.1.tgz", + "integrity": "sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ==", + "license": "ISC" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } } } } diff --git a/package.json b/package.json index 2c1e8b7..c3d985d 100644 --- a/package.json +++ b/package.json @@ -5,14 +5,22 @@ "scripts": { "dev": "astro dev", "build": "astro build", + "start": "node ./dist/server/entry.mjs", "preview": "astro preview", "astro": "astro" }, "dependencies": { + "@astrojs/node": "^9.5.2", + "@astrojs/ts-plugin": "^1.10.6", + "@starwind-ui/core": "^1.15.2", "@tabler/icons": "^3.36.1", "@tailwindcss/forms": "^0.5.11", "@tailwindcss/vite": "^4.1.18", "astro": "^5.16.9", + "embla-carousel": "^8.6.0", + "embla-carousel-autoplay": "^8.6.0", + "motion": "^12.26.2", + "starwind": "file:starwind-1.15.2.tgz", "tailwind-merge": "^3.4.0", "tailwind-variants": "^3.2.2", "tailwindcss": "^4.1.18", diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png new file mode 100644 index 0000000..62db97a Binary files /dev/null and b/public/apple-touch-icon.png differ diff --git a/public/favicon-96x96.png b/public/favicon-96x96.png new file mode 100644 index 0000000..42631da Binary files /dev/null and b/public/favicon-96x96.png differ diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..bb49f6b Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/favicon.svg b/public/favicon.svg index f157bd1..3402cdd 100644 --- a/public/favicon.svg +++ b/public/favicon.svg @@ -1,9 +1,57 @@ - - - - + + + + + + + + + \ No newline at end of file diff --git a/public/fonts/Inter_28pt-Bold.ttf b/public/fonts/Inter_28pt-Bold.ttf new file mode 100644 index 0000000..14db994 Binary files /dev/null and b/public/fonts/Inter_28pt-Bold.ttf differ diff --git a/public/fonts/Inter_28pt-Medium.ttf b/public/fonts/Inter_28pt-Medium.ttf new file mode 100644 index 0000000..00120fe Binary files /dev/null and b/public/fonts/Inter_28pt-Medium.ttf differ diff --git a/public/fonts/Inter_28pt-Regular.ttf b/public/fonts/Inter_28pt-Regular.ttf new file mode 100644 index 0000000..855b6f4 Binary files /dev/null and b/public/fonts/Inter_28pt-Regular.ttf differ diff --git a/public/fonts/Inter_28pt-SemiBold.ttf b/public/fonts/Inter_28pt-SemiBold.ttf new file mode 100644 index 0000000..8b84efc Binary files /dev/null and b/public/fonts/Inter_28pt-SemiBold.ttf differ diff --git a/public/fonts/Poppins-Bold.ttf b/public/fonts/Poppins-Bold.ttf new file mode 100644 index 0000000..1982f38 Binary files /dev/null and b/public/fonts/Poppins-Bold.ttf differ diff --git a/public/fonts/Poppins-Medium.ttf b/public/fonts/Poppins-Medium.ttf new file mode 100644 index 0000000..a590f5c Binary files /dev/null and b/public/fonts/Poppins-Medium.ttf differ diff --git a/public/fonts/Poppins-Regular.ttf b/public/fonts/Poppins-Regular.ttf new file mode 100644 index 0000000..0bda228 Binary files /dev/null and b/public/fonts/Poppins-Regular.ttf differ diff --git a/public/fonts/Poppins-SemiBold.ttf b/public/fonts/Poppins-SemiBold.ttf new file mode 100644 index 0000000..c30ad10 Binary files /dev/null and b/public/fonts/Poppins-SemiBold.ttf differ diff --git a/public/fonts/fonts/Inter_28pt-Bold.ttf b/public/fonts/fonts/Inter_28pt-Bold.ttf new file mode 100644 index 0000000..14db994 Binary files /dev/null and b/public/fonts/fonts/Inter_28pt-Bold.ttf differ diff --git a/public/fonts/fonts/Inter_28pt-Medium.ttf b/public/fonts/fonts/Inter_28pt-Medium.ttf new file mode 100644 index 0000000..00120fe Binary files /dev/null and b/public/fonts/fonts/Inter_28pt-Medium.ttf differ diff --git a/public/fonts/fonts/Inter_28pt-Regular.ttf b/public/fonts/fonts/Inter_28pt-Regular.ttf new file mode 100644 index 0000000..855b6f4 Binary files /dev/null and b/public/fonts/fonts/Inter_28pt-Regular.ttf differ diff --git a/public/fonts/fonts/Inter_28pt-SemiBold.ttf b/public/fonts/fonts/Inter_28pt-SemiBold.ttf new file mode 100644 index 0000000..8b84efc Binary files /dev/null and b/public/fonts/fonts/Inter_28pt-SemiBold.ttf differ diff --git a/public/fonts/fonts/Poppins-Bold.ttf b/public/fonts/fonts/Poppins-Bold.ttf new file mode 100644 index 0000000..1982f38 Binary files /dev/null and b/public/fonts/fonts/Poppins-Bold.ttf differ diff --git a/public/fonts/fonts/Poppins-Medium.ttf b/public/fonts/fonts/Poppins-Medium.ttf new file mode 100644 index 0000000..a590f5c Binary files /dev/null and b/public/fonts/fonts/Poppins-Medium.ttf differ diff --git a/public/fonts/fonts/Poppins-Regular.ttf b/public/fonts/fonts/Poppins-Regular.ttf new file mode 100644 index 0000000..0bda228 Binary files /dev/null and b/public/fonts/fonts/Poppins-Regular.ttf differ diff --git a/public/fonts/fonts/Poppins-SemiBold.ttf b/public/fonts/fonts/Poppins-SemiBold.ttf new file mode 100644 index 0000000..c30ad10 Binary files /dev/null and b/public/fonts/fonts/Poppins-SemiBold.ttf differ diff --git a/public/og-image.jpg b/public/og-image.jpg new file mode 100644 index 0000000..ead2fc9 Binary files /dev/null and b/public/og-image.jpg differ diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..11154fe --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: https://resuely.com/sitemap.xml diff --git a/public/site.webmanifest b/public/site.webmanifest new file mode 100644 index 0000000..b28bd9f --- /dev/null +++ b/public/site.webmanifest @@ -0,0 +1,21 @@ +{ + "name": "Resuely", + "short_name": "Resuely", + "icons": [ + { + "src": "/web-app-manifest-192x192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "/web-app-manifest-512x512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} \ No newline at end of file diff --git a/public/sitemap.xml b/public/sitemap.xml new file mode 100644 index 0000000..5a29ed2 --- /dev/null +++ b/public/sitemap.xml @@ -0,0 +1,6 @@ + + + + https://resuely.com/ + + diff --git a/public/web-app-manifest-192x192.png b/public/web-app-manifest-192x192.png new file mode 100644 index 0000000..e5ca106 Binary files /dev/null and b/public/web-app-manifest-192x192.png differ diff --git a/public/web-app-manifest-512x512.png b/public/web-app-manifest-512x512.png new file mode 100644 index 0000000..63e8f94 Binary files /dev/null and b/public/web-app-manifest-512x512.png differ diff --git a/src/assets/faq.jpg b/src/assets/faq.jpg new file mode 100644 index 0000000..f1b87c1 Binary files /dev/null and b/src/assets/faq.jpg differ diff --git a/src/assets/handy.jpg b/src/assets/handy.jpg new file mode 100644 index 0000000..22cba96 Binary files /dev/null and b/src/assets/handy.jpg differ diff --git a/src/assets/hero1.jpg b/src/assets/hero1.jpg new file mode 100644 index 0000000..96a6a89 Binary files /dev/null and b/src/assets/hero1.jpg differ diff --git a/src/assets/hero2.jpg b/src/assets/hero2.jpg new file mode 100644 index 0000000..76a9900 Binary files /dev/null and b/src/assets/hero2.jpg differ diff --git a/src/assets/hero3.jpg b/src/assets/hero3.jpg new file mode 100644 index 0000000..47e428c Binary files /dev/null and b/src/assets/hero3.jpg differ diff --git a/src/assets/hero4.jpg b/src/assets/hero4.jpg new file mode 100644 index 0000000..775b405 Binary files /dev/null and b/src/assets/hero4.jpg differ diff --git a/src/assets/hero5.jpg b/src/assets/hero5.jpg new file mode 100644 index 0000000..7f56500 Binary files /dev/null and b/src/assets/hero5.jpg differ diff --git a/src/assets/logo-dark.svg b/src/assets/logo-dark.svg new file mode 100644 index 0000000..07e0103 --- /dev/null +++ b/src/assets/logo-dark.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/logo-light.svg b/src/assets/logo-light.svg new file mode 100644 index 0000000..411a956 --- /dev/null +++ b/src/assets/logo-light.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/programming.jpg b/src/assets/programming.jpg new file mode 100644 index 0000000..9db37fe Binary files /dev/null and b/src/assets/programming.jpg differ diff --git a/src/components/TopBarDropdown.astro b/src/components/TopBarDropdown.astro new file mode 100644 index 0000000..83f6a7d --- /dev/null +++ b/src/components/TopBarDropdown.astro @@ -0,0 +1,27 @@ +--- +import { + Dropdown, + DropdownTrigger, + DropdownContent, + DropdownItem, + DropdownLabel, + DropdownSeparator +} from "@/components/starwind/dropdown"; +import { Button } from "@/components/starwind/button"; +import Hamburger from "@tabler/icons/outline/menu-2.svg"; +--- + + + + + + + + + Aplicaciones + + Handy + + diff --git a/src/components/starwind-pro/contact-02/Contact2.astro b/src/components/starwind-pro/contact-02/Contact2.astro new file mode 100644 index 0000000..65b5cc5 --- /dev/null +++ b/src/components/starwind-pro/contact-02/Contact2.astro @@ -0,0 +1,154 @@ +--- +import type { HTMLAttributes } from "astro/types"; +import { tv } from "tailwind-variants"; + +import { Button } from "@/components/starwind/button"; +import { Card, CardContent } from "@/components/starwind/card"; +import { Input } from "@/components/starwind/input"; +import { Label } from "@/components/starwind/label"; +import { Textarea } from "@/components/starwind/textarea"; + +interface Props extends HTMLAttributes<"section"> { + badge: string; + heading: string; + description: string; +} + +const sectionStyles = tv({ + base: "@container mx-auto w-full max-w-3xl px-4 py-24", +}); + +const { + badge, + heading, + description, + class: className, + ...rest +} = Astro.props; +--- + +
+
+ + {badge} + +

+ {heading} +

+

+ {description} +

+
+ + + +
+
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+ + diff --git a/src/components/starwind/textarea/index.ts b/src/components/starwind/textarea/index.ts new file mode 100644 index 0000000..f343c2c --- /dev/null +++ b/src/components/starwind/textarea/index.ts @@ -0,0 +1,9 @@ +import Textarea, { textarea } from "./Textarea.astro"; + +const TextareaVariants = { + textarea, +}; + +export { Textarea, TextareaVariants }; + +export default Textarea; diff --git a/src/layouts/BaseLayout.astro b/src/layouts/BaseLayout.astro index 1b7ed0b..078da42 100644 --- a/src/layouts/BaseLayout.astro +++ b/src/layouts/BaseLayout.astro @@ -1,15 +1,107 @@ --- +import "@/styles/starwind.css"; import "@/styles/global.css"; -import "../styles/global.css" +import Footer1 from "@/components/starwind-pro/footer-01/Footer1.astro"; +import TopBarDropdown from "@/components/TopBarDropdown.astro"; +import { Image } from "astro:assets"; +import LogoLight from "@/assets/logo-light.svg"; + +interface Props { + title: string; + description?: string; + image?: string; + url?: string; +} + +const { + title, + description = "Resuely", + image = "/og-image.jpg", + url = "https://resuely.com", +} = Astro.props; + +const siteUrl = Astro.site?.toString() ?? url; +const canonicalUrl = new URL(Astro.url.pathname, siteUrl).toString(); +const ogImageUrl = new URL(image, siteUrl).toString(); +const orgLogoUrl = new URL("/apple-touch-icon.png", siteUrl).toString(); + +const websiteSchema = { + "@context": "https://schema.org", + "@type": "WebSite", + name: "Resuely", + url: siteUrl, + inLanguage: "es-VE", +}; + +const organizationSchema = { + "@context": "https://schema.org", + "@type": "Organization", + name: "Resuely", + url: siteUrl, + logo: orgLogoUrl, + sameAs: ["https://git.rlugo.dev/resuely"], +}; --- + + {title} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+
+
+ +
- - diff --git a/src/pages/api/contact.ts b/src/pages/api/contact.ts new file mode 100644 index 0000000..ab737c7 --- /dev/null +++ b/src/pages/api/contact.ts @@ -0,0 +1,222 @@ +import type { APIRoute } from "astro"; + +export const prerender = false; + +type ApiErrorCode = + | "methodNotAllowed" + | "invalidRequest" + | "rateLimited" + | "notConfigured" + | "emailFailed"; + +type ApiErrorResponse = { + ok: false; + error: { + code: ApiErrorCode; + message: string; + }; +}; + +type ApiOkResponse = { + ok: true; +}; + +const respondJson = ( + status: number, + body: ApiOkResponse | ApiErrorResponse, +) => { + return new Response(JSON.stringify(body), { + status, + headers: { + "content-type": "application/json; charset=utf-8", + "cache-control": "no-store", + }, + }); +}; + +const getClientIp = (request: Request, fallbackIp: string) => { + const xff = request.headers.get("x-forwarded-for"); + if (xff) return xff.split(",")[0]?.trim() || fallbackIp; + const xri = request.headers.get("x-real-ip"); + if (xri) return xri.trim(); + return fallbackIp; +}; + +type RateLimitEntry = { + windowStartMs: number; + count: number; +}; + +const rateLimitWindowMs = 60_000; +const rateLimitMax = 5; +const rateLimitMap = new Map(); + +const isRateLimited = (key: string, nowMs: number) => { + const existing = rateLimitMap.get(key); + if (!existing) { + rateLimitMap.set(key, { windowStartMs: nowMs, count: 1 }); + return false; + } + + if (nowMs - existing.windowStartMs > rateLimitWindowMs) { + rateLimitMap.set(key, { windowStartMs: nowMs, count: 1 }); + return false; + } + + existing.count += 1; + rateLimitMap.set(key, existing); + return existing.count > rateLimitMax; +}; + +const normalizeText = (value: string, maxLen: number) => { + const collapsed = value.replace(/[\u0000-\u001F\u007F]/g, " ").trim(); + if (collapsed.length <= maxLen) return collapsed; + return collapsed.slice(0, maxLen); +}; + +const isEmailLike = (value: string) => { + const v = value.trim(); + if (v.length < 6 || v.length > 254) return false; + return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v); +}; + +const readBody = async (request: Request) => { + const contentType = request.headers.get("content-type") ?? ""; + + if ( + contentType.includes("application/x-www-form-urlencoded") || + contentType.includes("multipart/form-data") + ) { + const data = await request.formData(); + return { + name: String(data.get("name") ?? ""), + email: String(data.get("email") ?? ""), + subject: String(data.get("subject") ?? ""), + message: String(data.get("message") ?? ""), + }; + } + + if (contentType.includes("application/json")) { + const json: unknown = await request.json(); + if (typeof json !== "object" || json === null) return null; + const record = json as Record; + return { + name: typeof record.name === "string" ? record.name : "", + email: typeof record.email === "string" ? record.email : "", + subject: typeof record.subject === "string" ? record.subject : "", + message: typeof record.message === "string" ? record.message : "", + }; + } + + return null; +}; + +export const POST: APIRoute = async ({ request, clientAddress }) => { + const nowMs = Date.now(); + const ip = getClientIp(request, clientAddress); + const rateKey = `contact:${ip}`; + + if (isRateLimited(rateKey, nowMs)) { + return respondJson(429, { + ok: false, + error: { + code: "rateLimited", + message: "Demasiados intentos. Intenta de nuevo en un minuto.", + }, + }); + } + + const raw = await readBody(request); + if (!raw) { + return respondJson(400, { + ok: false, + error: { + code: "invalidRequest", + message: "Solicitud inválida.", + }, + }); + } + + const name = normalizeText(raw.name, 80); + const email = normalizeText(raw.email, 254); + const subject = normalizeText(raw.subject, 120); + const message = normalizeText(raw.message, 4000); + + if (!name || !email || !subject || !message || !isEmailLike(email)) { + return respondJson(400, { + ok: false, + error: { + code: "invalidRequest", + message: "Revisa los campos e inténtalo de nuevo.", + }, + }); + } + + const resendApiKey = import.meta.env.RESEND_API_KEY as string | undefined; + const contactToEmail = import.meta.env.CONTACT_TO_EMAIL as string | undefined; + const contactFromEmail = import.meta.env.CONTACT_FROM_EMAIL as + | string + | undefined; + + if (!resendApiKey || !contactToEmail || !contactFromEmail) { + return respondJson(501, { + ok: false, + error: { + code: "notConfigured", + message: "Contacto no está configurado todavía.", + }, + }); + } + + const emailText = [ + "Nuevo mensaje desde resuely.com", + "", + `Nombre: ${name}`, + `Correo: ${email}`, + `Asunto: ${subject}`, + "", + "Mensaje:", + message, + ].join("\n"); + + const resendPayload: Record = { + from: contactFromEmail, + to: contactToEmail, + subject: `[Resuely] ${subject}`, + text: emailText, + }; + resendPayload["reply_to"] = email; + + const response = await fetch("https://api.resend.com/emails", { + method: "POST", + headers: { + authorization: `Bearer ${resendApiKey}`, + "content-type": "application/json", + accept: "application/json", + }, + body: JSON.stringify(resendPayload), + }); + + if (!response.ok) { + console.log(await response.json()); + return respondJson(502, { + ok: false, + error: { + code: "emailFailed", + message: "No pudimos enviar tu mensaje. Intenta de nuevo más tarde.", + }, + }); + } + + return respondJson(200, { ok: true }); +}; + +export const ALL: APIRoute = async () => { + return respondJson(405, { + ok: false, + error: { + code: "methodNotAllowed", + message: "Método no permitido.", + }, + }); +}; diff --git a/src/pages/api/healthz.ts b/src/pages/api/healthz.ts new file mode 100644 index 0000000..7ec5492 --- /dev/null +++ b/src/pages/api/healthz.ts @@ -0,0 +1,11 @@ +import type { APIRoute } from "astro"; + +export const GET: APIRoute = async () => { + return new Response(JSON.stringify({ ok: true }), { + status: 200, + headers: { + "content-type": "application/json; charset=utf-8", + "cache-control": "no-store", + }, + }); +}; diff --git a/src/pages/index.astro b/src/pages/index.astro index bf3b87c..7c07fce 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -1,7 +1,83 @@ --- import BaseLayout from "../layouts/BaseLayout.astro" -import Feature2Demo from "@/components/starwind-pro/feature-02/Feature2Demo.astro" +import Hero, { type CarouselImage } from "@/components/starwind-pro/hero-09/Hero9.astro" +import Services7, { type Service } from "@/components/starwind-pro/services-07/Services7.astro" +import Contact2 from "@/components/starwind-pro/contact-02/Contact2.astro" +import Faq4, { type FaqItem } from "@/components/starwind-pro/faq-04/Faq4.astro" +import Img1 from "@/assets/hero1.jpg"; +import Img2 from "@/assets/hero2.jpg"; +import Img3 from "@/assets/hero3.jpg"; +import Img4 from "@/assets/hero4.jpg"; +import Img5 from "@/assets/hero5.jpg"; +import Handy from "@/assets/handy.jpg"; +import Programming from "@/assets/programming.jpg"; + +const carouselImages: CarouselImage[] = [ + {src: Img1.src, alt: "Paisaje de Venezuela"}, + {src: Img2.src, alt: "Ciudad en Venezuela"}, + {src: Img3.src, alt: "Montañas en Venezuela"}, + {src: Img4.src, alt: "Costa de Venezuela"}, + {src: Img5.src, alt: "Atardecer en Venezuela"}, +] + +const services: Service[] = [ + { title: "Handy", tagline: "Encuentra y contacta profesionales", image: Handy, href: "https://handy.resuely.com" }, + { title: "Próximamente", tagline: "Más soluciones simples en camino", image: Programming } +] + +const faqItems: FaqItem[] = [ + { title: "¿Qué es Resuely?", content: "Resuely es un conjunto de aplicaciones web gratuitas creadas por venezolanos para hacer más fácil el día a día. Entras, eliges una app y resuelves en minutos." }, + { title: "¿Por qué existe Resuely?", content: "Porque muchas herramientas son demasiado complicadas o vienen con cosas que no necesitas. Nosotros preferimos soluciones simples, claras y confiables." }, + { title: "¿Cuánto cuesta?", content: "Nada. Las apps de Resuely son gratis." }, + { title: "¿Necesito instalar algo?", content: "No. Resuely funciona desde el navegador, en tu teléfono o computadora." }, + { title: "¿Necesito crear una cuenta?", content: "Depende de la app. Algunas funcionan sin cuenta; otras te piden registrarte para guardar tu información y poder volver cuando lo necesites." }, + { title: "¿Qué pasa con mis datos?", content: "Usamos tu información solo para que la app funcione. No la vendemos ni la publicamos." }, + { title: "¿Puedo usar Resuely fuera de Venezuela?", content: "Sí. Puedes usar nuestras apps desde cualquier lugar con internet, aunque están pensadas para necesidades comunes en Venezuela." }, + { title: "¿Puedo sugerir una app o una mejora?", content: "Sí. Usa el formulario de contacto y cuéntanos tu idea. Leemos todo." }, + { title: "¿Dónde pido ayuda si algo no me funciona?", content: "Usa el formulario de contacto y descríbenos qué intentaste hacer y qué viste en pantalla." }, +] + +const faqSchema = { + "@context": "https://schema.org", + "@type": "FAQPage", + mainEntity: faqItems.map((item) => ({ + "@type": "Question", + name: item.title, + acceptedAnswer: { + "@type": "Answer", + text: item.content, + }, + })), +}; --- - - + + + + + + + diff --git a/src/styles/fonts.css b/src/styles/fonts.css new file mode 100644 index 0000000..9e6a9fa --- /dev/null +++ b/src/styles/fonts.css @@ -0,0 +1,63 @@ +@font-face { + font-family: 'Inter'; + src: url('/fonts/Inter_28pt-Regular.ttf') format('truetype'); + font-weight: 400; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Inter'; + src: url('/fonts/Inter_28pt-Medium.ttf') format('truetype'); + font-weight: 500; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Inter'; + src: url('/fonts/Inter_28pt-SemiBold.ttf') format('truetype'); + font-weight: 600; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Inter'; + src: url('/fonts/Inter_28pt-Bold.ttf') format('truetype'); + font-weight: 700; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Poppins'; + src: url('/fonts/Poppins-Regular.ttf') format('truetype'); + font-weight: 400; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Poppins'; + src: url('/fonts/Poppins-Medium.ttf') format('truetype'); + font-weight: 500; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Poppins'; + src: url('/fonts/Poppins-SemiBold.ttf') format('truetype'); + font-weight: 600; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Poppins'; + src: url('/fonts/Poppins-Bold.ttf') format('truetype'); + font-weight: 700; + font-style: normal; + font-display: swap; +} diff --git a/src/styles/global.css b/src/styles/global.css index 23089c8..8c21d6a 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -1,167 +1,16 @@ @import "tailwindcss"; -@import "tw-animate-css"; -@plugin "@tailwindcss/forms"; -@custom-variant dark (&:where(.dark, .dark *)); +@import "./fonts.css"; @theme { - --animate-accordion-down: accordion-down 0.2s ease-out; - --animate-accordion-up: accordion-up 0.2s ease-out; + --font-sans: 'Inter', system-ui, sans-serif; + --font-heading: 'Poppins', sans-serif; - @keyframes accordion-down { - from { - height: 0; - } - to { - height: var(--starwind-accordion-content-height); - } - } - - @keyframes accordion-up { - from { - height: var(--starwind-accordion-content-height); - } - to { - height: 0; - } - } -} - -@theme inline { - --color-background: var(--background); - --color-foreground: var(--foreground); - --color-card: var(--card); - --color-card-foreground: var(--card-foreground); - --color-popover: var(--popover); - --color-popover-foreground: var(--popover-foreground); - --color-primary: var(--primary); - --color-primary-foreground: var(--primary-foreground); - --color-primary-accent: var(--primary-accent); - --color-secondary: var(--secondary); - --color-secondary-foreground: var(--secondary-foreground); - --color-secondary-accent: var(--secondary-accent); - --color-muted: var(--muted); - --color-muted-foreground: var(--muted-foreground); - --color-accent: var(--accent); - --color-accent-foreground: var(--accent-foreground); - --color-info: var(--info); - --color-info-foreground: var(--info-foreground); - --color-success: var(--success); - --color-success-foreground: var(--success-foreground); - --color-warning: var(--warning); - --color-warning-foreground: var(--warning-foreground); - --color-error: var(--error); - --color-error-foreground: var(--error-foreground); - --color-border: var(--border); - --color-input: var(--input); - --color-outline: var(--outline); - - --radius-xs: calc(var(--radius) - 0.375rem); - --radius-sm: calc(var(--radius) - 0.25rem); - --radius-md: calc(var(--radius) - 0.125rem); - --radius-lg: var(--radius); - --radius-xl: calc(var(--radius) + 0.25rem); - --radius-2xl: calc(var(--radius) + 0.5rem); - --radius-3xl: calc(var(--radius) + 1rem); - - --color-sidebar: var(--sidebar-background); - --color-sidebar-foreground: var(--sidebar-foreground); - --color-sidebar-primary: var(--sidebar-primary); - --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); - --color-sidebar-accent: var(--sidebar-accent); - --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); - --color-sidebar-border: var(--sidebar-border); - --color-sidebar-outline: var(--sidebar-outline); -} - -:root { - --background: var(--color-white); - --foreground: var(--color-neutral-950); - --card: var(--color-white); - --card-foreground: var(--color-neutral-950); - --popover: var(--color-white); - --popover-foreground: var(--color-neutral-950); - --primary: var(--color-blue-700); - --primary-foreground: var(--color-neutral-50); - --primary-accent: var(--color-blue-700); - --secondary: var(--color-fuchsia-700); - --secondary-foreground: var(--color-neutral-50); - --secondary-accent: var(--color-fuchsia-700); - --muted: var(--color-neutral-100); - --muted-foreground: var(--color-neutral-600); - --accent: var(--color-neutral-100); - --accent-foreground: var(--color-neutral-900); - --info: var(--color-sky-300); - --info-foreground: var(--color-sky-950); - --success: var(--color-green-300); - --success-foreground: var(--color-green-950); - --warning: var(--color-amber-300); - --warning-foreground: var(--color-amber-950); - --error: var(--color-red-700); - --error-foreground: var(--color-neutral-50); - --border: var(--color-neutral-200); - --input: var(--color-neutral-200); - --outline: var(--color-neutral-400); - --radius: 0.625rem; - - /* sidebar variables */ - --sidebar-background: var(--color-neutral-50); - --sidebar-foreground: var(--color-neutral-950); - --sidebar-primary: var(--color-blue-700); - --sidebar-primary-foreground: var(--color-neutral-50); - --sidebar-accent: var(--color-neutral-100); - --sidebar-accent-foreground: var(--color-neutral-900); - --sidebar-border: var(--color-neutral-200); - --sidebar-outline: var(--color-neutral-400); -} - -.dark { - --background: var(--color-neutral-950); - --foreground: var(--color-neutral-50); - --card: var(--color-neutral-900); - --card-foreground: var(--color-neutral-50); - --popover: var(--color-neutral-800); - --popover-foreground: var(--color-neutral-50); - --primary: var(--color-blue-700); - --primary-foreground: var(--color-neutral-50); - --primary-accent: var(--color-blue-400); - --secondary: var(--color-fuchsia-700); - --secondary-foreground: var(--color-neutral-50); - --secondary-accent: var(--color-fuchsia-400); - --muted: var(--color-neutral-800); - --muted-foreground: var(--color-neutral-400); - --accent: var(--color-neutral-700); - --accent-foreground: var(--color-neutral-100); - --info: var(--color-sky-300); - --info-foreground: var(--color-sky-950); - --success: var(--color-green-300); - --success-foreground: var(--color-green-950); - --warning: var(--color-amber-300); - --warning-foreground: var(--color-amber-950); - --error: var(--color-red-800); - --error-foreground: var(--color-neutral-50); - --border: --alpha(var(--color-neutral-50) / 10%); - --input: --alpha(var(--color-neutral-50) / 15%); - --outline: var(--color-neutral-500); - - /* sidebars variables */ - --sidebar-background: var(--color-neutral-900); - --sidebar-foreground: var(--color-neutral-50); - --sidebar-primary: var(--color-blue-700); - --sidebar-primary-foreground: var(--color-neutral-50); - --sidebar-accent: var(--color-neutral-800); - --sidebar-accent-foreground: var(--color-neutral-100); - --sidebar-border: var(--color-neutral-800); - --sidebar-outline: var(--color-neutral-600); + --tracking-resuely: 0.2em; } @layer base { - * { - @apply border-border outline-outline/50; - } - body { - @apply bg-background text-foreground scheme-light dark:scheme-dark; - } - button { - @apply cursor-pointer; + html { + scroll-behavior: smooth; } } + diff --git a/src/styles/starwind.css b/src/styles/starwind.css new file mode 100644 index 0000000..23089c8 --- /dev/null +++ b/src/styles/starwind.css @@ -0,0 +1,167 @@ +@import "tailwindcss"; +@import "tw-animate-css"; +@plugin "@tailwindcss/forms"; +@custom-variant dark (&:where(.dark, .dark *)); + +@theme { + --animate-accordion-down: accordion-down 0.2s ease-out; + --animate-accordion-up: accordion-up 0.2s ease-out; + + @keyframes accordion-down { + from { + height: 0; + } + to { + height: var(--starwind-accordion-content-height); + } + } + + @keyframes accordion-up { + from { + height: var(--starwind-accordion-content-height); + } + to { + height: 0; + } + } +} + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-primary-accent: var(--primary-accent); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-secondary-accent: var(--secondary-accent); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-info: var(--info); + --color-info-foreground: var(--info-foreground); + --color-success: var(--success); + --color-success-foreground: var(--success-foreground); + --color-warning: var(--warning); + --color-warning-foreground: var(--warning-foreground); + --color-error: var(--error); + --color-error-foreground: var(--error-foreground); + --color-border: var(--border); + --color-input: var(--input); + --color-outline: var(--outline); + + --radius-xs: calc(var(--radius) - 0.375rem); + --radius-sm: calc(var(--radius) - 0.25rem); + --radius-md: calc(var(--radius) - 0.125rem); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 0.25rem); + --radius-2xl: calc(var(--radius) + 0.5rem); + --radius-3xl: calc(var(--radius) + 1rem); + + --color-sidebar: var(--sidebar-background); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-outline: var(--sidebar-outline); +} + +:root { + --background: var(--color-white); + --foreground: var(--color-neutral-950); + --card: var(--color-white); + --card-foreground: var(--color-neutral-950); + --popover: var(--color-white); + --popover-foreground: var(--color-neutral-950); + --primary: var(--color-blue-700); + --primary-foreground: var(--color-neutral-50); + --primary-accent: var(--color-blue-700); + --secondary: var(--color-fuchsia-700); + --secondary-foreground: var(--color-neutral-50); + --secondary-accent: var(--color-fuchsia-700); + --muted: var(--color-neutral-100); + --muted-foreground: var(--color-neutral-600); + --accent: var(--color-neutral-100); + --accent-foreground: var(--color-neutral-900); + --info: var(--color-sky-300); + --info-foreground: var(--color-sky-950); + --success: var(--color-green-300); + --success-foreground: var(--color-green-950); + --warning: var(--color-amber-300); + --warning-foreground: var(--color-amber-950); + --error: var(--color-red-700); + --error-foreground: var(--color-neutral-50); + --border: var(--color-neutral-200); + --input: var(--color-neutral-200); + --outline: var(--color-neutral-400); + --radius: 0.625rem; + + /* sidebar variables */ + --sidebar-background: var(--color-neutral-50); + --sidebar-foreground: var(--color-neutral-950); + --sidebar-primary: var(--color-blue-700); + --sidebar-primary-foreground: var(--color-neutral-50); + --sidebar-accent: var(--color-neutral-100); + --sidebar-accent-foreground: var(--color-neutral-900); + --sidebar-border: var(--color-neutral-200); + --sidebar-outline: var(--color-neutral-400); +} + +.dark { + --background: var(--color-neutral-950); + --foreground: var(--color-neutral-50); + --card: var(--color-neutral-900); + --card-foreground: var(--color-neutral-50); + --popover: var(--color-neutral-800); + --popover-foreground: var(--color-neutral-50); + --primary: var(--color-blue-700); + --primary-foreground: var(--color-neutral-50); + --primary-accent: var(--color-blue-400); + --secondary: var(--color-fuchsia-700); + --secondary-foreground: var(--color-neutral-50); + --secondary-accent: var(--color-fuchsia-400); + --muted: var(--color-neutral-800); + --muted-foreground: var(--color-neutral-400); + --accent: var(--color-neutral-700); + --accent-foreground: var(--color-neutral-100); + --info: var(--color-sky-300); + --info-foreground: var(--color-sky-950); + --success: var(--color-green-300); + --success-foreground: var(--color-green-950); + --warning: var(--color-amber-300); + --warning-foreground: var(--color-amber-950); + --error: var(--color-red-800); + --error-foreground: var(--color-neutral-50); + --border: --alpha(var(--color-neutral-50) / 10%); + --input: --alpha(var(--color-neutral-50) / 15%); + --outline: var(--color-neutral-500); + + /* sidebars variables */ + --sidebar-background: var(--color-neutral-900); + --sidebar-foreground: var(--color-neutral-50); + --sidebar-primary: var(--color-blue-700); + --sidebar-primary-foreground: var(--color-neutral-50); + --sidebar-accent: var(--color-neutral-800); + --sidebar-accent-foreground: var(--color-neutral-100); + --sidebar-border: var(--color-neutral-800); + --sidebar-outline: var(--color-neutral-600); +} + +@layer base { + * { + @apply border-border outline-outline/50; + } + body { + @apply bg-background text-foreground scheme-light dark:scheme-dark; + } + button { + @apply cursor-pointer; + } +} diff --git a/starwind.config.json b/starwind.config.json index bea6059..6acf6c4 100644 --- a/starwind.config.json +++ b/starwind.config.json @@ -1,15 +1,51 @@ { "$schema": "https://starwind.dev/config-schema.json", "tailwind": { - "css": "src/styles/global.css", + "css": "src/styles/starwind.css", "baseColor": "neutral", "cssVariables": true }, "componentDir": "src/components", "components": [ + { + "name": "navbar", + "version": "1.0.0" + }, + { + "name": "button", + "version": "2.3.0" + }, + { + "name": "input", + "version": "1.3.1" + }, + { + "name": "accordion", + "version": "1.3.3" + }, + { + "name": "carousel", + "version": "1.0.1" + }, + { + "name": "dropdown", + "version": "1.2.3" + }, + { + "name": "label", + "version": "1.2.0" + }, + { + "name": "textarea", + "version": "1.3.1" + }, { "name": "image", "version": "1.0.0" + }, + { + "name": "card", + "version": "2.0.0" } ] } diff --git a/tsconfig.json b/tsconfig.json index f36744a..0e9e45b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,8 +11,15 @@ "baseUrl": ".", "paths": { "@/*": [ - "src/*" + "src/*", + "components/*", + "assets/*" ] - } + }, + "plugins": [ + { + "name": "@astrojs/ts-plugin" + }, + ], } }