commit 17859e7de56630d549237ff898b4eace60cd45e0 Author: kudindmitriy Date: Fri Apr 10 02:44:26 2026 +0300 Initial commit diff --git a/.env b/.env new file mode 100644 index 0000000..905944d --- /dev/null +++ b/.env @@ -0,0 +1,4 @@ + + NEXT_PUBLIC_API_URL=https://dev.api.webild.io + NEXT_PUBLIC_PROJECT_ID=20e1ab85-8867-4496-baca-ef453bcf7615 + \ No newline at end of file diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml new file mode 100644 index 0000000..9aa1e08 --- /dev/null +++ b/.gitea/workflows/build.yml @@ -0,0 +1,37 @@ +name: Code Check + +on: + workflow_dispatch: + +jobs: + check: + runs-on: ubuntu-latest + + steps: + - name: Checkout + run: git clone --depth 1 --branch ${{ gitea.ref_name }} ${{ gitea.server_url }}/${{ gitea.repository }}.git . || exit 1 + + - name: Install dependencies + run: | + if [ -d "/var/node_modules_cache/node_modules" ]; then + ln -s /var/node_modules_cache/node_modules ./node_modules + else + npm ci --prefer-offline --no-audit + fi + timeout-minutes: 5 + + - name: TypeScript check + run: npm run typecheck 2>&1 | tee build.log + timeout-minutes: 3 + + - name: ESLint check + run: npm run lint 2>&1 | tee -a build.log + timeout-minutes: 3 + + - name: Upload build log on failure + if: failure() + uses: actions/upload-artifact@v3 + with: + name: build-log + path: build.log + retention-days: 1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d70bb9c --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules +dist +.DS_Store +*.local diff --git a/THEME_PROVIDER_OPTIONS.txt b/THEME_PROVIDER_OPTIONS.txt new file mode 100644 index 0000000..6edb716 --- /dev/null +++ b/THEME_PROVIDER_OPTIONS.txt @@ -0,0 +1,58 @@ +================================================================================ + THEME OPTIONS (v4) +================================================================================ + +STYLE PROPS +----------- + +1. borderRadius + • "rounded" + • "soft" + • "pill" + +2. contentWidth + • "small" + • "compact" + • "medium" + • "mediumLarge" + +3. cardStyle + • "solid" + • "outline" + • "gradient-mesh" + • "gradient-radial" + • "inset" + • "glass-elevated" + • "glass-depth" + • "gradient-bordered" + • "layered-gradient" + • "soft-shadow" + • "subtle-shadow" + • "elevated-border" + • "inner-glow" + • "spotlight" + +4. primaryButtonStyle + • "gradient" + • "shadow" + • "flat" + • "radial-glow" + • "diagonal-gradient" + • "double-inset" + • "primary-glow" + • "inset-glow" + • "soft-glow" + • "glass-shimmer" + • "neon-outline" + • "lifted" + • "depth-layers" + • "accent-edge" + • "metallic" + +5. secondaryButtonStyle + • "glass" + • "solid" + • "layered" + • "radial-glow" + +================================================================================ diff --git a/colorThemes.json b/colorThemes.json new file mode 100644 index 0000000..2e4fccc --- /dev/null +++ b/colorThemes.json @@ -0,0 +1,677 @@ +{ + "lightTheme": { + "minimalDarkBlue": { + "--background": "#ffffff", + "--card": "#f9f9f9", + "--foreground": "#000612e6", + "--primary-cta": "#15479c", + "--secondary-cta": "#f9f9f9", + "--accent": "#e2e2e2", + "--background-accent": "#c4c4c4", + "--primary-cta-text": "#ffffff", + "--secondary-cta-text": "#000612e6" + }, + "minimalDarkGreen": { + "--background": "#ffffff", + "--card": "#f9f9f9", + "--foreground": "#000f06e6", + "--primary-cta": "#0a7039", + "--secondary-cta": "#f9f9f9", + "--accent": "#e2e2e2", + "--background-accent": "#c4c4c4", + "--primary-cta-text": "#ffffff", + "--secondary-cta-text": "#000f06e6" + }, + "minimalLightRed": { + "--background": "#ffffff", + "--card": "#f9f9f9", + "--foreground": "#120006e6", + "--primary-cta": "#e63946", + "--secondary-cta": "#f9f9f9", + "--accent": "#e2e2e2", + "--background-accent": "#c4c4c4", + "--primary-cta-text": "#ffffff", + "--secondary-cta-text": "#120006e6" + }, + "minimalBrightBlue": { + "--background": "#ffffff", + "--card": "#f9f9f9", + "--foreground": "#000612e6", + "--primary-cta": "#106EFB", + "--secondary-cta": "#f9f9f9", + "--accent": "#e2e2e2", + "--background-accent": "#106EFB", + "--primary-cta-text": "#ffffff", + "--secondary-cta-text": "#000612e6" + }, + "minimalBrightOrange": { + "--background": "#ffffff", + "--card": "#f9f9f9", + "--foreground": "#120a00e6", + "--primary-cta": "#E34400", + "--secondary-cta": "#f9f9f9", + "--accent": "#e2e2e2", + "--background-accent": "#E34400", + "--primary-cta-text": "#ffffff", + "--secondary-cta-text": "#120a00e6" + }, + "minimalGoldenOrange": { + "--background": "#ffffff", + "--card": "#f9f9f9", + "--foreground": "#120a00e6", + "--primary-cta": "#FF7B05", + "--secondary-cta": "#f9f9f9", + "--accent": "#e2e2e2", + "--background-accent": "#FF7B05", + "--primary-cta-text": "#ffffff", + "--secondary-cta-text": "#120a00e6" + }, + "minimalLightOrange": { + "--background": "#ffffff", + "--card": "#f9f9f9", + "--foreground": "#120a00e6", + "--primary-cta": "#ff8c42", + "--secondary-cta": "#f9f9f9", + "--accent": "#e2e2e2", + "--background-accent": "#c4c4c4", + "--primary-cta-text": "#ffffff", + "--secondary-cta-text": "#120a00e6" + }, + "darkBlue": { + "--background": "#f5faff", + "--card": "#f1f8ff", + "--foreground": "#001122", + "--primary-cta": "#15479c", + "--secondary-cta": "#ffffff", + "--accent": "#a8cce8", + "--background-accent": "#7ba3cf", + "--primary-cta-text": "#f5faff", + "--secondary-cta-text": "#001122" + }, + "darkGreen": { + "--background": "#fafffb", + "--card": "#f7fffa", + "--foreground": "#001a0a", + "--primary-cta": "#0a7039", + "--secondary-cta": "#ffffff", + "--accent": "#a8d9be", + "--background-accent": "#6bbf8e", + "--primary-cta-text": "#fafffb", + "--secondary-cta-text": "#001a0a" + }, + "lightRed": { + "--background": "#fffafa", + "--card": "#fff7f7", + "--foreground": "#1a0000", + "--primary-cta": "#e63946", + "--secondary-cta": "#ffffff", + "--accent": "#f5c4c7", + "--background-accent": "#f09199", + "--primary-cta-text": "#fffafa", + "--secondary-cta-text": "#1a0000" + }, + "lightPurple": { + "--background": "#fbfaff", + "--card": "#f7f5ff", + "--foreground": "#0f0022", + "--primary-cta": "#8b5cf6", + "--secondary-cta": "#ffffff", + "--accent": "#d8cef5", + "--background-accent": "#c4a8f9", + "--primary-cta-text": "#fbfaff", + "--secondary-cta-text": "#0f0022" + }, + "warmCream": { + "--background": "#f6f0e9", + "--card": "#efe7dd", + "--foreground": "#2b180a", + "--primary-cta": "#2b180a", + "--secondary-cta": "#efe7dd", + "--accent": "#94877c", + "--background-accent": "#afa094", + "--primary-cta-text": "#f6f0e9", + "--secondary-cta-text": "#2b180a" + }, + "grayBlueAccent": { + "--background": "#f5f5f5", + "--card": "#ffffff", + "--foreground": "#1c1c1c", + "--primary-cta": "#1c1c1c", + "--secondary-cta": "#ffffff", + "--accent": "#15479c", + "--background-accent": "#a8cce8", + "--primary-cta-text": "#f5f5f5", + "--secondary-cta-text": "#1c1c1c" + }, + "grayGreenAccent": { + "--background": "#f5f5f5", + "--card": "#ffffff", + "--foreground": "#1c1c1c", + "--primary-cta": "#1c1c1c", + "--secondary-cta": "#ffffff", + "--accent": "#159c49", + "--background-accent": "#a8e8ba", + "--primary-cta-text": "#f5f5f5", + "--secondary-cta-text": "#1c1c1c" + }, + "grayRedAccent": { + "--background": "#f5f5f5", + "--card": "#ffffff", + "--foreground": "#1c1c1c", + "--primary-cta": "#1c1c1c", + "--secondary-cta": "#ffffff", + "--accent": "#e63946", + "--background-accent": "#e8bea8", + "--primary-cta-text": "#f5f5f5", + "--secondary-cta-text": "#1c1c1c" + }, + "grayPurpleAccent": { + "--background": "#f5f5f5", + "--card": "#ffffff", + "--foreground": "#1c1c1c", + "--primary-cta": "#1c1c1c", + "--secondary-cta": "#ffffff", + "--accent": "#6139e6", + "--background-accent": "#b3a8e8", + "--primary-cta-text": "#f5f5f5", + "--secondary-cta-text": "#1c1c1c" + }, + "warmBeige": { + "--background": "#efebe5", + "--card": "#f7f2ea", + "--foreground": "#000000", + "--primary-cta": "#000000", + "--secondary-cta": "#ffffff", + "--accent": "#ffffff", + "--background-accent": "#e1b875", + "--primary-cta-text": "#efebe5", + "--secondary-cta-text": "#000000" + }, + "grayTealGreen": { + "--background": "#f5f5f5", + "--card": "#ffffff", + "--foreground": "#1c1c1c", + "--primary-cta": "#1f514c", + "--secondary-cta": "#ffffff", + "--accent": "#159c49", + "--background-accent": "#a8e8ba", + "--primary-cta-text": "#f5f5f5", + "--secondary-cta-text": "#1c1c1c" + }, + "grayNavyBlue": { + "--background": "#f5f5f5", + "--card": "#ffffff", + "--foreground": "#1c1c1c", + "--primary-cta": "#1f3251", + "--secondary-cta": "#ffffff", + "--accent": "#15479c", + "--background-accent": "#a8cce8", + "--primary-cta-text": "#f5f5f5", + "--secondary-cta-text": "#1c1c1c" + }, + "grayBurgundyRed": { + "--background": "#f5f5f5", + "--card": "#ffffff", + "--foreground": "#1c1c1c", + "--primary-cta": "#511f1f", + "--secondary-cta": "#ffffff", + "--accent": "#e63946", + "--background-accent": "#e8bea8", + "--primary-cta-text": "#f5f5f5", + "--secondary-cta-text": "#1c1c1c" + }, + "grayIndigoPurple": { + "--background": "#f5f5f5", + "--card": "#ffffff", + "--foreground": "#1c1c1c", + "--primary-cta": "#341f51", + "--secondary-cta": "#ffffff", + "--accent": "#6139e6", + "--background-accent": "#b3a8e8", + "--primary-cta-text": "#f5f5f5", + "--secondary-cta-text": "#1c1c1c" + }, + "warmgrayPink": { + "--background": "#f7f6f7", + "--card": "#ffffff", + "--foreground": "#1b0c25", + "--primary-cta": "#1b0c25", + "--secondary-cta": "#ffffff", + "--accent": "#ff93e4", + "--background-accent": "#e8a8c3", + "--primary-cta-text": "#f7f6f7", + "--secondary-cta-text": "#1b0c25" + }, + "warmgrayOrange": { + "--background": "#f7f6f7", + "--card": "#ffffff", + "--foreground": "#25190c", + "--primary-cta": "#ff6207", + "--secondary-cta": "#ffffff", + "--accent": "#ffce93", + "--background-accent": "#e8cfa8", + "--primary-cta-text": "#f7f6f7", + "--secondary-cta-text": "#25190c" + }, + "warmgrayBlue": { + "--background": "#f7f6f7", + "--card": "#ffffff", + "--foreground": "#0c1325", + "--primary-cta": "#0798ff", + "--secondary-cta": "#ffffff", + "--accent": "#93c7ff", + "--background-accent": "#a8cde8", + "--primary-cta-text": "#f7f6f7", + "--secondary-cta-text": "#0c1325" + }, + "warmgrayIndigo": { + "--background": "#f7f6f7", + "--card": "#ffffff", + "--foreground": "#0c1325", + "--primary-cta": "#0b07ff", + "--secondary-cta": "#ffffff", + "--accent": "#93b7ff", + "--background-accent": "#a8bae8", + "--primary-cta-text": "#f7f6f7", + "--secondary-cta-text": "#0c1325" + }, + "lavenderPeach": { + "--background": "#e3deea", + "--card": "#ffffff", + "--foreground": "#27231f", + "--primary-cta": "#27231f", + "--secondary-cta": "#ffffff", + "--accent": "#c68a62", + "--background-accent": "#c68a62", + "--primary-cta-text": "#e3deea", + "--secondary-cta-text": "#27231f" + }, + "lavenderBlue": { + "--background": "#e3deea", + "--card": "#ffffff", + "--foreground": "#1f2027", + "--primary-cta": "#1f2027", + "--secondary-cta": "#ffffff", + "--accent": "#627dc6", + "--background-accent": "#627dc6", + "--primary-cta-text": "#e3deea", + "--secondary-cta-text": "#1f2027" + }, + "warmStone": { + "--background": "#f5f4ef", + "--card": "#dad6cd", + "--foreground": "#2a2928", + "--primary-cta": "#2a2928", + "--secondary-cta": "#ecebea", + "--accent": "#ffffff", + "--background-accent": "#c6b180", + "--primary-cta-text": "#f5f4ef", + "--secondary-cta-text": "#2a2928" + }, + "warmStoneGray": { + "--background": "#f5f4f0", + "--card": "#ffffff", + "--foreground": "#1a1a1a", + "--primary-cta": "#2c2c2c", + "--secondary-cta": "#f5f4f0", + "--accent": "#8a8a8a", + "--background-accent": "#e8e6e1", + "--primary-cta-text": "#f5f4f0", + "--secondary-cta-text": "#1a1a1a" + }, + "warmGreen": { + "--background": "#fffefe", + "--card": "#f6f7f4", + "--foreground": "#080908", + "--primary-cta": "#0e3a29", + "--secondary-cta": "#e7eecd", + "--accent": "#35c18b", + "--background-accent": "#ecebe4", + "--primary-cta-text": "#fffefe", + "--secondary-cta-text": "#080908" + }, + "warmSand": { + "--background": "#fcf6ec", + "--card": "#f3ede2", + "--foreground": "#2e2521", + "--primary-cta": "#2e2521", + "--secondary-cta": "#ffffff", + "--accent": "#b2a28b", + "--background-accent": "#b2a28b", + "--primary-cta-text": "#fcf6ec", + "--secondary-cta-text": "#2e2521" + }, + "warmgrayRed": { + "--background": "#f7f6f7", + "--card": "#ffffff", + "--foreground": "#250c0d", + "--primary-cta": "#b82b40", + "--secondary-cta": "#ffffff", + "--accent": "#b90941", + "--background-accent": "#e8a8b6", + "--primary-cta-text": "#f7f6f7", + "--secondary-cta-text": "#250c0d" + } + }, + "darkTheme": { + "minimal": { + "--background": "#0a0a0a", + "--card": "#1a1a1a", + "--foreground": "#ffffffe6", + "--primary-cta": "#e6e6e6", + "--secondary-cta": "#1a1a1a", + "--accent": "#737373", + "--background-accent": "#737373", + "--primary-cta-text": "#0a0a0a", + "--secondary-cta-text": "#ffffffe6" + }, + "minimalLightBlue": { + "--background": "#0a0a0a", + "--card": "#1a1a1a", + "--foreground": "#f0f8ffe6", + "--primary-cta": "#cee7ff", + "--secondary-cta": "#1a1a1a", + "--accent": "#737373", + "--background-accent": "#737373", + "--primary-cta-text": "#0a0a0a", + "--secondary-cta-text": "#f0f8ffe6" + }, + "minimalLightGreen": { + "--background": "#0a0a0a", + "--card": "#1a1a1a", + "--foreground": "#f5fffae6", + "--primary-cta": "#80da9b", + "--secondary-cta": "#1a1a1a", + "--accent": "#737373", + "--background-accent": "#737373", + "--primary-cta-text": "#0a0a0a", + "--secondary-cta-text": "#f5fffae6" + }, + "minimalLightRed": { + "--background": "#0a0a0a", + "--card": "#1a1a1a", + "--foreground": "#fff5f5e6", + "--primary-cta": "#ff7a7a", + "--secondary-cta": "#1a1a1a", + "--accent": "#737373", + "--background-accent": "#737373", + "--primary-cta-text": "#0a0a0a", + "--secondary-cta-text": "#fff5f5e6" + }, + "minimalLightPurple": { + "--background": "#0a0a0a", + "--card": "#1a1a1a", + "--foreground": "#f8f5ffe6", + "--primary-cta": "#c89bff", + "--secondary-cta": "#1a1a1a", + "--accent": "#737373", + "--background-accent": "#737373", + "--primary-cta-text": "#0a0a0a", + "--secondary-cta-text": "#f8f5ffe6" + }, + "lightBlueWhite": { + "--background": "#010912", + "--card": "#152840", + "--foreground": "#e6f0ff", + "--primary-cta": "#cee7ff", + "--secondary-cta": "#0e1a29", + "--accent": "#3f5c79", + "--background-accent": "#004a93", + "--primary-cta-text": "#010912", + "--secondary-cta-text": "#ffffff" + }, + "lime": { + "--background": "#0a0a0a", + "--card": "#1a1a1a", + "--foreground": "#f5f5f5", + "--primary-cta": "#dfff1c", + "--secondary-cta": "#1a1a1a", + "--accent": "#8b9a1b", + "--background-accent": "#5d6b00", + "--primary-cta-text": "#0a0a0a", + "--secondary-cta-text": "#ffffff" + }, + "gold": { + "--background": "#0a0a0a", + "--card": "#1a1a1a", + "--foreground": "#f5f5f5", + "--primary-cta": "#ffdf7d", + "--secondary-cta": "#1a1a1a", + "--accent": "#b8860b", + "--background-accent": "#8b6914", + "--primary-cta-text": "#0a0a0a", + "--secondary-cta-text": "#ffffff" + }, + "crimson": { + "--background": "#0a0a0a", + "--card": "#1a1a1a", + "--foreground": "#f5f5f5", + "--primary-cta": "#ff0000", + "--secondary-cta": "#1a1a1a", + "--accent": "#991b1b", + "--background-accent": "#7f1d1d", + "--primary-cta-text": "#ffffff", + "--secondary-cta-text": "#ffffff" + }, + "midnightIce": { + "--background": "#000000", + "--card": "#0c0c0c", + "--foreground": "#ffffff", + "--primary-cta": "#cee7ff", + "--secondary-cta": "#000000", + "--accent": "#535353", + "--background-accent": "#CEE7FF", + "--primary-cta-text": "#000000", + "--secondary-cta-text": "#ffffff" + }, + "midnightBlue": { + "--background": "#000000", + "--card": "#0c0c0c", + "--foreground": "#ffffff", + "--primary-cta": "#106EFB", + "--secondary-cta": "#000000", + "--accent": "#535353", + "--background-accent": "#106EFB", + "--primary-cta-text": "#ffffff", + "--secondary-cta-text": "#ffffff" + }, + "blueOrangeAccent": { + "--background": "#0a0a0a", + "--card": "#1a1a1a", + "--foreground": "#ffffff", + "--primary-cta": "#1f7cff", + "--secondary-cta": "#010101", + "--accent": "#1f7cff", + "--background-accent": "#f96b2f", + "--primary-cta-text": "#ffffff", + "--secondary-cta-text": "#ffffff" + }, + "orangeBlueAccent": { + "--background": "#0a0a0a", + "--card": "#1a1a1a", + "--foreground": "#ffffff", + "--primary-cta": "#e34400", + "--secondary-cta": "#010101", + "--accent": "#ff7b05", + "--background-accent": "#106efb", + "--primary-cta-text": "#ffffff", + "--secondary-cta-text": "#ffffff" + }, + "minimalBrightOrange": { + "--background": "#0a0a0a", + "--card": "#1a1a1a", + "--foreground": "#ffffff", + "--primary-cta": "#e34400", + "--secondary-cta": "#010101", + "--accent": "#737373", + "--background-accent": "#e34400", + "--primary-cta-text": "#ffffff", + "--secondary-cta-text": "#ffffff" + }, + "minimalLightOrange": { + "--background": "#0a0a0a", + "--card": "#1a1a1a", + "--foreground": "#fffaf5e6", + "--primary-cta": "#ffaa70", + "--secondary-cta": "#1a1a1a", + "--accent": "#737373", + "--background-accent": "#737373", + "--primary-cta-text": "#0a0a0a", + "--secondary-cta-text": "#fffaf5e6" + }, + "minimalLightYellow": { + "--background": "#0a0a0a", + "--card": "#1a1a1a", + "--foreground": "#fffffae6", + "--primary-cta": "#fde047", + "--secondary-cta": "#1a1a1a", + "--accent": "#737373", + "--background-accent": "#737373", + "--primary-cta-text": "#0a0a0a", + "--secondary-cta-text": "#fffffae6" + }, + "lightBlue": { + "--background": "#010912", + "--card": "#152840", + "--foreground": "#e6f0ff", + "--primary-cta": "#cee7ff", + "--secondary-cta": "#0e1a29", + "--accent": "#3f5c79", + "--background-accent": "#004a93", + "--primary-cta-text": "#010912", + "--secondary-cta-text": "#e6f0ff" + }, + "lightGreen": { + "--background": "#000802", + "--card": "#0b1a0b", + "--foreground": "#e6ffe6", + "--primary-cta": "#80da9b", + "--secondary-cta": "#07170b", + "--accent": "#38714a", + "--background-accent": "#2c6541", + "--primary-cta-text": "#000802", + "--secondary-cta-text": "#e6ffe6" + }, + "lightRed": { + "--background": "#080000", + "--card": "#1e0d0d", + "--foreground": "#ffe6e6", + "--primary-cta": "#ff7a7a", + "--secondary-cta": "#1e0909", + "--accent": "#7b4242", + "--background-accent": "#65292c", + "--primary-cta-text": "#080000", + "--secondary-cta-text": "#ffe6e6" + }, + "darkRed": { + "--background": "#060000", + "--card": "#1d0d0d", + "--foreground": "#ffe6e6", + "--primary-cta": "#ff3d4a", + "--secondary-cta": "#1f0a0a", + "--accent": "#7b2d2d", + "--background-accent": "#b8111f", + "--primary-cta-text": "#ffffff", + "--secondary-cta-text": "#ffe6e6" + }, + "lightPurple": { + "--background": "#050012", + "--card": "#040121", + "--foreground": "#f0e6ff", + "--primary-cta": "#c89bff", + "--secondary-cta": "#1d123b", + "--accent": "#684f7b", + "--background-accent": "#65417c", + "--primary-cta-text": "#050012", + "--secondary-cta-text": "#f0e6ff" + }, + "lightOrange": { + "--background": "#080200", + "--card": "#1a0d0b", + "--foreground": "#ffe6d5", + "--primary-cta": "#ffaa70", + "--secondary-cta": "#170b07", + "--accent": "#7b5e4a", + "--background-accent": "#b8541e", + "--primary-cta-text": "#080200", + "--secondary-cta-text": "#ffe6d5" + }, + "deepBlue": { + "--background": "#020617", + "--card": "#0f172a", + "--foreground": "#e2e8f0", + "--primary-cta": "#c4d8f9", + "--secondary-cta": "#041633", + "--accent": "#2d30f3", + "--background-accent": "#1d4ed8", + "--primary-cta-text": "#020617", + "--secondary-cta-text": "#e2e8f0" + }, + "violet": { + "--background": "#030128", + "--card": "#241f48", + "--foreground": "#ffffff", + "--primary-cta": "#ffffff", + "--secondary-cta": "#131136", + "--accent": "#44358a", + "--background-accent": "#b597fe", + "--primary-cta-text": "#030128", + "--secondary-cta-text": "#d5d4f6" + }, + "ruby": { + "--background": "#000000", + "--card": "#481f1f", + "--foreground": "#ffffff", + "--primary-cta": "#ffffff", + "--secondary-cta": "#361311", + "--accent": "#51000b", + "--background-accent": "#ff2231", + "--primary-cta-text": "#280101", + "--secondary-cta-text": "#f6d4d4" + }, + "emerald": { + "--background": "#000000", + "--card": "#1f4035", + "--foreground": "#ffffff", + "--primary-cta": "#ffffff", + "--secondary-cta": "#0d2b1f", + "--accent": "#0d5238", + "--background-accent": "#10b981", + "--primary-cta-text": "#051a12", + "--secondary-cta-text": "#d4f6e8" + }, + "indigo": { + "--background": "#000000", + "--card": "#1f1f40", + "--foreground": "#ffffff", + "--primary-cta": "#ffffff", + "--secondary-cta": "#0d0d2b", + "--accent": "#3d2880", + "--background-accent": "#663cff", + "--primary-cta-text": "#0a051a", + "--secondary-cta-text": "#d4d4f6" + }, + "forest": { + "--background": "#000000", + "--card": "#1a2f1d", + "--foreground": "#ffffff", + "--primary-cta": "#ffffff", + "--secondary-cta": "#0d200f", + "--accent": "#1a3d1f", + "--background-accent": "#355e3b", + "--primary-cta-text": "#0a1a0c", + "--secondary-cta-text": "#d4f6d8" + }, + "mint": { + "--background": "#000000", + "--card": "#1a2a1a", + "--foreground": "#ffffff", + "--primary-cta": "#ffffff", + "--secondary-cta": "#0d1a0d", + "--accent": "#2d4a2d", + "--background-accent": "#c1e1c1", + "--primary-cta-text": "#0a150a", + "--secondary-cta-text": "#e1f6e1" + } + } +} diff --git a/cssOptions.json b/cssOptions.json new file mode 100644 index 0000000..25689e5 --- /dev/null +++ b/cssOptions.json @@ -0,0 +1,41 @@ +{ + "cards": { + "solid": "background: var(--color-card);", + "outline": "background: var(--color-card);\nborder: 1px solid color-mix(in srgb, var(--color-accent) 25%, transparent);", + "gradient-mesh": "background:\n radial-gradient(at 0% 0%, color-mix(in srgb, var(--color-accent) 15%, transparent) 0px, transparent 50%),\n radial-gradient(at 100% 0%, color-mix(in srgb, var(--color-accent) 10%, transparent) 0px, transparent 50%),\n radial-gradient(at 100% 100%, color-mix(in srgb, var(--color-accent) 20%, transparent) 0px, transparent 50%),\n radial-gradient(at 0% 100%, color-mix(in srgb, var(--color-accent) 12%, transparent) 0px, transparent 50%),\n var(--color-card);", + "gradient-radial": "background: radial-gradient(circle at center, color-mix(in srgb, var(--color-card) 100%, var(--color-accent) 20%) 0%, var(--color-card) 90%);", + "inset": "background: color-mix(in srgb, var(--color-card) 95%, var(--color-accent) 5%);\nbox-shadow:\n inset 2px 2px 4px color-mix(in srgb, var(--color-foreground) 8%, transparent),\n inset -2px -2px 4px color-mix(in srgb, var(--color-background) 20%, transparent);", + "glass-elevated": "backdrop-filter: blur(8px);\nbackground: linear-gradient(to bottom right, color-mix(in srgb, var(--color-card) 80%, transparent), color-mix(in srgb, var(--color-card) 40%, transparent));\nbox-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);\nborder: 1px solid var(--color-card);", + "glass-depth": "background: color-mix(in srgb, var(--color-card) 80%, transparent);\nbackdrop-filter: blur(14px);\nbox-shadow:\n inset 0 0 20px 0 color-mix(in srgb, var(--color-accent) 7.5%, transparent);\nborder: 1px solid color-mix(in srgb, var(--color-accent) 7.5%, transparent);", + "gradient-bordered": "background: linear-gradient(180deg, color-mix(in srgb, var(--color-card) 100%, var(--color-accent) 5%) -35%, var(--color-card) 65%);\nbox-shadow: 0px 0px 10px 4px color-mix(in srgb, var(--color-accent) 4%, transparent);\nborder: 1px solid color-mix(in srgb, var(--color-accent) 15%, transparent);", + "layered-gradient": "background:\n linear-gradient(color-mix(in srgb, var(--color-accent) 6%, transparent) 0%, transparent 59.26%),\n linear-gradient(var(--color-card) 0%, var(--color-card) 100%),\n var(--color-card);\nbox-shadow:\n 20px 18px 7px color-mix(in srgb, var(--color-accent) 0%, transparent),\n 2px 2px 2px color-mix(in srgb, var(--color-accent) 6.5%, transparent),\n 1px 1px 2px color-mix(in srgb, var(--color-accent) 2%, transparent);\nborder: 2px solid var(--color-secondary-cta);", + "soft-shadow": "background: var(--color-card);\nbox-shadow: color-mix(in srgb, var(--color-accent) 10%, transparent) 0px 0.706592px 0.706592px -0.666667px, color-mix(in srgb, var(--color-accent) 8%, transparent) 0px 1.80656px 1.80656px -1.33333px, color-mix(in srgb, var(--color-accent) 7%, transparent) 0px 3.62176px 3.62176px -2px, color-mix(in srgb, var(--color-accent) 7%, transparent) 0px 6.8656px 6.8656px -2.66667px, color-mix(in srgb, var(--color-accent) 5%, transparent) 0px 13.6468px 13.6468px -3.33333px, color-mix(in srgb, var(--color-accent) 2%, transparent) 0px 30px 30px -4px, var(--color-background) 0px 3px 1px 0px inset;", + "subtle-shadow": "background: var(--color-card);\nbox-shadow: color-mix(in srgb, var(--color-foreground) 5%, transparent) 0px 4px 32px 0px;", + "elevated-border": "background: linear-gradient(180deg, color-mix(in srgb, var(--color-card) 100%, var(--color-foreground) 3%) 0%, var(--color-card) 100%);\nbox-shadow: 0 1px 0 0 color-mix(in srgb, var(--color-foreground) 8%, transparent), 0 4px 6px -1px color-mix(in srgb, var(--color-foreground) 5%, transparent), 0 10px 15px -3px color-mix(in srgb, var(--color-foreground) 4%, transparent);\nborder: 1px solid color-mix(in srgb, var(--color-foreground) 6%, transparent);", + "inner-glow": "background: var(--color-card);\nbox-shadow: inset 0 0 30px 0 color-mix(in srgb, var(--color-foreground) 4%, transparent), inset 0 1px 0 0 color-mix(in srgb, var(--color-foreground) 8%, transparent), 0 4px 12px -4px color-mix(in srgb, var(--color-foreground) 8%, transparent);", + "spotlight": "background:\n radial-gradient(ellipse at 0% 0%, color-mix(in srgb, var(--color-accent) 20%, transparent) 0%, transparent 50%),\n var(--color-card);\nbox-shadow: inset 1px 1px 0 0 color-mix(in srgb, var(--color-foreground) 10%, transparent), 0 4px 16px -4px color-mix(in srgb, var(--color-foreground) 10%, transparent);" + }, + "primaryButtons": { + "gradient": "background: linear-gradient(to bottom, color-mix(in srgb, var(--color-primary-cta) 75%, transparent), var(--color-primary-cta));\nbox-shadow: color-mix(in srgb, var(--color-background) 25%, transparent) 0px 1px 1px 0px inset, color-mix(in srgb, var(--color-primary-cta) 15%, transparent) 3px 3px 3px 0px;", + "shadow": "background: var(--color-primary-cta);\nbox-shadow: 2.10837px 3.16256px 9.48767px color-mix(in srgb, var(--color-primary-cta) 40%, transparent);", + "flat": "background: var(--color-primary-cta);", + "radial-glow": "background:\n radial-gradient(circle at 0% 0%, color-mix(in srgb, var(--color-background) 32.5%, transparent) 0%, transparent 45%),\n radial-gradient(circle at 100% 100%, color-mix(in srgb, var(--color-background) 32.5%, transparent) 0%, transparent 45%),\n var(--color-primary-cta);\nbox-shadow: 2.10837px 3.16256px 9.48767px color-mix(in srgb, var(--color-accent) 30%, transparent);", + "diagonal-gradient": "background: linear-gradient(to bottom right, color-mix(in srgb, var(--color-primary-cta) 80%, transparent), var(--color-foreground));\nbox-shadow: 2.10837px 3.16256px 9.48767px color-mix(in srgb, var(--color-accent) 30%, transparent);", + "double-inset": "background: var(--color-primary-cta);\nbox-shadow: color-mix(in srgb, var(--color-background) 15%, transparent) 0px 4px 10px 0px inset, color-mix(in srgb, var(--color-background) 15%, transparent) 0px -4px 8px 0px inset;", + "primary-glow": "background: var(--color-primary-cta);\nbox-shadow: color-mix(in srgb, var(--color-background) 20%, transparent) 0px 3px 1px 0px inset, color-mix(in srgb, var(--color-primary-cta) 13%, transparent) 0px 0.839802px 0.503881px -0.3125px, color-mix(in srgb, var(--color-primary-cta) 13%, transparent) 0px 1.99048px 1.19429px -0.625px, color-mix(in srgb, var(--color-primary-cta) 13%, transparent) 0px 3.63084px 2.1785px -0.9375px, color-mix(in srgb, var(--color-primary-cta) 13%, transparent) 0px 6.03627px 3.62176px -1.25px, color-mix(in srgb, var(--color-primary-cta) 13%, transparent) 0px 9.74808px 5.84885px -1.5625px, color-mix(in srgb, var(--color-primary-cta) 13%, transparent) 0px 15.9566px 9.57398px -1.875px, color-mix(in srgb, var(--color-primary-cta) 13%, transparent) 0px 27.4762px 16.4857px -2.1875px, color-mix(in srgb, var(--color-primary-cta) 13%, transparent) 0px 50px 30px -2.5px;", + "inset-glow": "background: linear-gradient(180deg, color-mix(in srgb, var(--color-primary-cta) 65%, var(--color-background)) -35%, var(--color-primary-cta) 65%);\nbox-shadow: 0 10px 18px -7px color-mix(in srgb, var(--color-background) 50%, transparent), inset 0 1px 0 0 color-mix(in srgb, var(--color-foreground) 15%, transparent);\nborder: 1px solid color-mix(in srgb, var(--color-foreground) 20%, transparent);", + "soft-glow": "background: radial-gradient(ellipse at 50% -20%, color-mix(in srgb, var(--color-primary-cta) 70%, var(--color-foreground)) 0%, var(--color-primary-cta) 70%);\nbox-shadow: 0 8px 24px -6px color-mix(in srgb, var(--color-primary-cta) 35%, transparent), inset 0 1px 0 0 color-mix(in srgb, var(--color-foreground) 20%, transparent);", + "glass-shimmer": "background: linear-gradient(165deg, color-mix(in srgb, var(--color-primary-cta) 85%, var(--color-foreground)) 0%, var(--color-primary-cta) 40%, color-mix(in srgb, var(--color-primary-cta) 90%, var(--color-background)) 100%);\nbox-shadow: inset 0 1px 1px 0 color-mix(in srgb, var(--color-foreground) 25%, transparent), inset 0 -1px 1px 0 color-mix(in srgb, var(--color-background) 15%, transparent), 0 4px 12px -2px color-mix(in srgb, var(--color-primary-cta) 25%, transparent);", + "neon-outline": "background: var(--color-primary-cta);\nbox-shadow: 0 0 5px color-mix(in srgb, var(--color-accent) 50%, transparent), 0 0 15px color-mix(in srgb, var(--color-accent) 30%, transparent), 0 0 30px color-mix(in srgb, var(--color-accent) 15%, transparent), inset 0 0 8px color-mix(in srgb, var(--color-accent) 10%, transparent);", + "lifted": "background: linear-gradient(180deg, color-mix(in srgb, var(--color-primary-cta) 95%, var(--color-foreground)) 0%, var(--color-primary-cta) 50%, color-mix(in srgb, var(--color-primary-cta) 95%, var(--color-background)) 100%);\nbox-shadow: inset 0 2px 3px 0 color-mix(in srgb, var(--color-foreground) 20%, transparent), inset 0 -2px 3px 0 color-mix(in srgb, var(--color-background) 25%, transparent), 0 2px 4px -1px color-mix(in srgb, var(--color-background) 40%, transparent);", + "depth-layers": "background: var(--color-primary-cta);\nbox-shadow: 0 1px 2px color-mix(in srgb, var(--color-primary-cta) 20%, transparent), 0 2px 4px color-mix(in srgb, var(--color-primary-cta) 20%, transparent), 0 4px 8px color-mix(in srgb, var(--color-primary-cta) 15%, transparent), 0 8px 16px color-mix(in srgb, var(--color-primary-cta) 10%, transparent), 0 16px 32px color-mix(in srgb, var(--color-primary-cta) 5%, transparent);", + "accent-edge": "background: linear-gradient(180deg, var(--color-primary-cta) 0%, color-mix(in srgb, var(--color-primary-cta) 90%, var(--color-background)) 100%);\nbox-shadow: 0 0 0 1px color-mix(in srgb, var(--color-accent) 60%, transparent), 0 4px 12px -2px color-mix(in srgb, var(--color-accent) 35%, transparent), inset 0 1px 0 0 color-mix(in srgb, var(--color-foreground) 20%, transparent);", + "metallic": "background: linear-gradient(135deg, color-mix(in srgb, var(--color-primary-cta) 80%, var(--color-foreground)) 0%, var(--color-primary-cta) 25%, color-mix(in srgb, var(--color-primary-cta) 90%, var(--color-background)) 50%, var(--color-primary-cta) 75%, color-mix(in srgb, var(--color-primary-cta) 85%, var(--color-foreground)) 100%);\nbox-shadow: inset 0 1px 0 0 color-mix(in srgb, var(--color-foreground) 30%, transparent), 0 3px 8px -2px color-mix(in srgb, var(--color-background) 50%, transparent);" + }, + "secondaryButtons": { + "glass": "backdrop-filter: blur(8px);\nbackground: linear-gradient(to bottom right, color-mix(in srgb, var(--color-secondary-cta) 80%, transparent), var(--color-secondary-cta));\nbox-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);\nborder: 1px solid var(--color-secondary-cta);", + "solid": "background: var(--color-secondary-cta);", + "layered": "background:\n linear-gradient(color-mix(in srgb, var(--color-accent) 5%, transparent) 0%, transparent 59.26%),\n linear-gradient(var(--color-secondary-cta), var(--color-secondary-cta)),\n linear-gradient(var(--color-secondary-cta), var(--color-secondary-cta)),\n linear-gradient(color-mix(in srgb, var(--color-accent) 5%, transparent) 0%, transparent 59.26%),\n linear-gradient(color-mix(in srgb, var(--color-secondary-cta) 60%, transparent), color-mix(in srgb, var(--color-secondary-cta) 60%, transparent)),\n var(--color-secondary-cta);\nbox-shadow:\n 2.10837px 3.16256px 9.48767px color-mix(in srgb, var(--color-accent) 10%, transparent);\nborder: 1px solid var(--color-secondary-cta);", + "radial-glow": "background:\n radial-gradient(circle at 0% 0%, color-mix(in srgb, var(--color-accent) 15%, transparent) 0%, transparent 40%),\n radial-gradient(circle at 100% 100%, color-mix(in srgb, var(--color-accent) 15%, transparent) 0%, transparent 40%),\n var(--color-secondary-cta);\nbox-shadow: 2.10837px 3.16256px 9.48767px color-mix(in srgb, var(--color-accent) 10%, transparent);" + } +} diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..5e6b472 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,23 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' +import { defineConfig, globalIgnores } from 'eslint/config' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + js.configs.recommended, + tseslint.configs.recommended, + reactHooks.configs.flat.recommended, + reactRefresh.configs.vite, + ], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + }, +]) diff --git a/fontThemes.json b/fontThemes.json new file mode 100644 index 0000000..e14bbca --- /dev/null +++ b/fontThemes.json @@ -0,0 +1,237 @@ +{ + "singleFonts": { + "interTight": { + "name": "Inter Tight", + "import": "import { Inter_Tight } from \"next/font/google\";", + "initialization": "const interTight = Inter_Tight({\n variable: \"--font-inter-tight\",\n subsets: [\"latin\"],\n weight: [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"],\n});", + "className": "${interTight.variable}", + "cssVariable": "font-family: var(--font-inter-tight), sans-serif;" + }, + "inter": { + "name": "Inter", + "import": "import { Inter } from \"next/font/google\";", + "initialization": "const inter = Inter({\n variable: \"--font-inter\",\n subsets: [\"latin\"],\n});", + "className": "${inter.variable}", + "cssVariable": "font-family: var(--font-inter), sans-serif;" + }, + "poppins": { + "name": "Poppins", + "import": "import { Poppins } from \"next/font/google\";", + "initialization": "const poppins = Poppins({\n variable: \"--font-poppins\",\n subsets: [\"latin\"],\n weight: [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"],\n});", + "className": "${poppins.variable}", + "cssVariable": "font-family: var(--font-poppins), sans-serif;" + }, + "montserrat": { + "name": "Montserrat", + "import": "import { Montserrat } from \"next/font/google\";", + "initialization": "const montserrat = Montserrat({\n variable: \"--font-montserrat\",\n subsets: [\"latin\"],\n});", + "className": "${montserrat.variable}", + "cssVariable": "font-family: var(--font-montserrat), sans-serif;" + }, + "roboto": { + "name": "Roboto", + "import": "import { Roboto } from \"next/font/google\";", + "initialization": "const roboto = Roboto({\n variable: \"--font-roboto\",\n subsets: [\"latin\"],\n weight: [\"100\", \"300\", \"400\", \"500\", \"700\", \"900\"],\n});", + "className": "${roboto.variable}", + "cssVariable": "font-family: var(--font-roboto), sans-serif;" + }, + "openSans": { + "name": "Open Sans", + "import": "import { Open_Sans } from \"next/font/google\";", + "initialization": "const openSans = Open_Sans({\n variable: \"--font-open-sans\",\n subsets: [\"latin\"],\n});", + "className": "${openSans.variable}", + "cssVariable": "font-family: var(--font-open-sans), sans-serif;" + }, + "lato": { + "name": "Lato", + "import": "import { Lato } from \"next/font/google\";", + "initialization": "const lato = Lato({\n variable: \"--font-lato\",\n subsets: [\"latin\"],\n weight: [\"100\", \"300\", \"400\", \"700\", \"900\"],\n});", + "className": "${lato.variable}", + "cssVariable": "font-family: var(--font-lato), sans-serif;" + }, + "dmSans": { + "name": "DM Sans", + "import": "import { DM_Sans } from \"next/font/google\";", + "initialization": "const dmSans = DM_Sans({\n variable: \"--font-dm-sans\",\n subsets: [\"latin\"],\n});", + "className": "${dmSans.variable}", + "cssVariable": "font-family: var(--font-dm-sans), sans-serif;" + }, + "manrope": { + "name": "Manrope", + "import": "import { Manrope } from \"next/font/google\";", + "initialization": "const manrope = Manrope({\n variable: \"--font-manrope\",\n subsets: [\"latin\"],\n});", + "className": "${manrope.variable}", + "cssVariable": "font-family: var(--font-manrope), sans-serif;" + }, + "sourceSans3": { + "name": "Source Sans 3", + "import": "import { Source_Sans_3 } from \"next/font/google\";", + "initialization": "const sourceSans3 = Source_Sans_3({\n variable: \"--font-source-sans-3\",\n subsets: [\"latin\"],\n});", + "className": "${sourceSans3.variable}", + "cssVariable": "font-family: var(--font-source-sans-3), sans-serif;" + }, + "publicSans": { + "name": "Public Sans", + "import": "import { Public_Sans } from \"next/font/google\";", + "initialization": "const publicSans = Public_Sans({\n variable: \"--font-public-sans\",\n subsets: [\"latin\"],\n});", + "className": "${publicSans.variable}", + "cssVariable": "font-family: var(--font-public-sans), sans-serif;" + }, + "mulish": { + "name": "Mulish", + "import": "import { Mulish } from \"next/font/google\";", + "initialization": "const mulish = Mulish({\n variable: \"--font-mulish\",\n subsets: [\"latin\"],\n});", + "className": "${mulish.variable}", + "cssVariable": "font-family: var(--font-mulish), sans-serif;" + }, + "nunito": { + "name": "Nunito", + "import": "import { Nunito } from \"next/font/google\";", + "initialization": "const nunito = Nunito({\n variable: \"--font-nunito\",\n subsets: [\"latin\"],\n});", + "className": "${nunito.variable}", + "cssVariable": "font-family: var(--font-nunito), sans-serif;" + }, + "nunitoSans": { + "name": "Nunito Sans", + "import": "import { Nunito_Sans } from \"next/font/google\";", + "initialization": "const nunitoSans = Nunito_Sans({\n variable: \"--font-nunito-sans\",\n subsets: [\"latin\"],\n});", + "className": "${nunitoSans.variable}", + "cssVariable": "font-family: var(--font-nunito-sans), sans-serif;" + }, + "raleway": { + "name": "Raleway", + "import": "import { Raleway } from \"next/font/google\";", + "initialization": "const raleway = Raleway({\n variable: \"--font-raleway\",\n subsets: [\"latin\"],\n});", + "className": "${raleway.variable}", + "cssVariable": "font-family: var(--font-raleway), sans-serif;" + }, + "archivo": { + "name": "Archivo", + "import": "import { Archivo } from \"next/font/google\";", + "initialization": "const archivo = Archivo({\n variable: \"--font-archivo\",\n subsets: [\"latin\"],\n});", + "className": "${archivo.variable}", + "cssVariable": "font-family: var(--font-archivo), sans-serif;" + }, + "figtree": { + "name": "Figtree", + "import": "import { Figtree } from \"next/font/google\";", + "initialization": "const figtree = Figtree({\n variable: \"--font-figtree\",\n subsets: [\"latin\"],\n});", + "className": "${figtree.variable}", + "cssVariable": "font-family: var(--font-figtree), sans-serif;" + } + }, + "fontPairings": { + "interOpenSans": { + "name": "Inter + Open Sans", + "description": "Neutral headings with friendly body. Clean and approachable.", + "headingFont": "inter", + "bodyFont": "openSans", + "imports": "import { Inter } from \"next/font/google\";\nimport { Open_Sans } from \"next/font/google\";", + "initializations": "const inter = Inter({\n variable: \"--font-inter\",\n subsets: [\"latin\"],\n});\n\nconst openSans = Open_Sans({\n variable: \"--font-open-sans\",\n subsets: [\"latin\"],\n});", + "classNames": "${inter.variable} ${openSans.variable}", + "globalsCss": { + "instructions": "Update the font-family property within the existing CSS rules in globals.css (@layer base section)", + "bodyRule": "body {\n /* ... existing properties ... */\n font-family: var(--font-open-sans), sans-serif;\n}", + "headingsRule": "h1, h2, h3, h4, h5, h6 {\n font-family: var(--font-inter), sans-serif;\n}", + "bodyFontFamily": "var(--font-open-sans), sans-serif", + "headingsFontFamily": "var(--font-inter), sans-serif" + } + }, + "dmSansInter": { + "name": "DM Sans + Inter", + "description": "Modern geometric headings with neutral body. Contemporary and clean.", + "headingFont": "dmSans", + "bodyFont": "inter", + "imports": "import { DM_Sans } from \"next/font/google\";\nimport { Inter } from \"next/font/google\";", + "initializations": "const dmSans = DM_Sans({\n variable: \"--font-dm-sans\",\n subsets: [\"latin\"],\n});\n\nconst inter = Inter({\n variable: \"--font-inter\",\n subsets: [\"latin\"],\n});", + "classNames": "${dmSans.variable} ${inter.variable}", + "globalsCss": { + "instructions": "Update the font-family property within the existing CSS rules in globals.css (@layer base section)", + "bodyRule": "body {\n /* ... existing properties ... */\n font-family: var(--font-inter), sans-serif;\n}", + "headingsRule": "h1, h2, h3, h4, h5, h6 {\n font-family: var(--font-dm-sans), sans-serif;\n}", + "bodyFontFamily": "var(--font-inter), sans-serif", + "headingsFontFamily": "var(--font-dm-sans), sans-serif" + } + }, + "manropeDmSans": { + "name": "Manrope + DM Sans", + "description": "Geometric headings with clean body. Modern and professional.", + "headingFont": "manrope", + "bodyFont": "dmSans", + "imports": "import { Manrope } from \"next/font/google\";\nimport { DM_Sans } from \"next/font/google\";", + "initializations": "const manrope = Manrope({\n variable: \"--font-manrope\",\n subsets: [\"latin\"],\n});\n\nconst dmSans = DM_Sans({\n variable: \"--font-dm-sans\",\n subsets: [\"latin\"],\n});", + "classNames": "${manrope.variable} ${dmSans.variable}", + "globalsCss": { + "instructions": "Update the font-family property within the existing CSS rules in globals.css (@layer base section)", + "bodyRule": "body {\n /* ... existing properties ... */\n font-family: var(--font-dm-sans), sans-serif;\n}", + "headingsRule": "h1, h2, h3, h4, h5, h6 {\n font-family: var(--font-manrope), sans-serif;\n}", + "bodyFontFamily": "var(--font-dm-sans), sans-serif", + "headingsFontFamily": "var(--font-manrope), sans-serif" + } + }, + "publicSansInter": { + "name": "Public Sans + Inter", + "description": "Government-inspired headings with neutral body. Professional and trustworthy.", + "headingFont": "publicSans", + "bodyFont": "inter", + "imports": "import { Public_Sans } from \"next/font/google\";\nimport { Inter } from \"next/font/google\";", + "initializations": "const publicSans = Public_Sans({\n variable: \"--font-public-sans\",\n subsets: [\"latin\"],\n});\n\nconst inter = Inter({\n variable: \"--font-inter\",\n subsets: [\"latin\"],\n});", + "classNames": "${publicSans.variable} ${inter.variable}", + "globalsCss": { + "instructions": "Update the font-family property within the existing CSS rules in globals.css (@layer base section)", + "bodyRule": "body {\n /* ... existing properties ... */\n font-family: var(--font-inter), sans-serif;\n}", + "headingsRule": "h1, h2, h3, h4, h5, h6 {\n font-family: var(--font-public-sans), sans-serif;\n}", + "bodyFontFamily": "var(--font-inter), sans-serif", + "headingsFontFamily": "var(--font-public-sans), sans-serif" + } + }, + "mulishInter": { + "name": "Mulish + Inter", + "description": "Minimal headings with neutral body. Clean and modern.", + "headingFont": "mulish", + "bodyFont": "inter", + "imports": "import { Mulish } from \"next/font/google\";\nimport { Inter } from \"next/font/google\";", + "initializations": "const mulish = Mulish({\n variable: \"--font-mulish\",\n subsets: [\"latin\"],\n});\n\nconst inter = Inter({\n variable: \"--font-inter\",\n subsets: [\"latin\"],\n});", + "classNames": "${mulish.variable} ${inter.variable}", + "globalsCss": { + "instructions": "Update the font-family property within the existing CSS rules in globals.css (@layer base section)", + "bodyRule": "body {\n /* ... existing properties ... */\n font-family: var(--font-inter), sans-serif;\n}", + "headingsRule": "h1, h2, h3, h4, h5, h6 {\n font-family: var(--font-mulish), sans-serif;\n}", + "bodyFontFamily": "var(--font-inter), sans-serif", + "headingsFontFamily": "var(--font-mulish), sans-serif" + } + }, + "montserratInter": { + "name": "Montserrat + Inter", + "description": "Geometric sans-serif headings with neutral body. Popular and reliable.", + "headingFont": "montserrat", + "bodyFont": "inter", + "imports": "import { Montserrat } from \"next/font/google\";\nimport { Inter } from \"next/font/google\";", + "initializations": "const montserrat = Montserrat({\n variable: \"--font-montserrat\",\n subsets: [\"latin\"],\n});\n\nconst inter = Inter({\n variable: \"--font-inter\",\n subsets: [\"latin\"],\n});", + "classNames": "${montserrat.variable} ${inter.variable}", + "globalsCss": { + "instructions": "Update the font-family property within the existing CSS rules in globals.css (@layer base section)", + "bodyRule": "body {\n /* ... existing properties ... */\n font-family: var(--font-inter), sans-serif;\n}", + "headingsRule": "h1, h2, h3, h4, h5, h6 {\n font-family: var(--font-montserrat), sans-serif;\n}", + "bodyFontFamily": "var(--font-inter), sans-serif", + "headingsFontFamily": "var(--font-montserrat), sans-serif" + } + }, + "libreBaskervilleInter": { + "name": "Libre Baskerville + Inter", + "description": "Classic serif headings with neutral body. Elegant and readable.", + "headingFont": "libreBaskerville", + "bodyFont": "inter", + "imports": "import { Libre_Baskerville } from \"next/font/google\";\nimport { Inter } from \"next/font/google\";", + "initializations": "const libreBaskerville = Libre_Baskerville({\n variable: \"--font-libre-baskerville\",\n subsets: [\"latin\"],\n weight: [\"400\", \"700\"],\n});\n\nconst inter = Inter({\n variable: \"--font-inter\",\n subsets: [\"latin\"],\n});", + "classNames": "${libreBaskerville.variable} ${inter.variable}", + "globalsCss": { + "instructions": "Update the font-family property within the existing CSS rules in globals.css (@layer base section)", + "bodyRule": "body {\n /* ... existing properties ... */\n font-family: var(--font-inter), sans-serif;\n}", + "headingsRule": "h1, h2, h3, h4, h5, h6 {\n font-family: var(--font-libre-baskerville), serif;\n}", + "bodyFontFamily": "var(--font-inter), sans-serif", + "headingsFontFamily": "var(--font-libre-baskerville), serif" + } + } + } +} diff --git a/index.html b/index.html new file mode 100644 index 0000000..c7652cd --- /dev/null +++ b/index.html @@ -0,0 +1,13 @@ + + + + + + + Webild Components + + +
+ + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..ab38e4f --- /dev/null +++ b/package.json @@ -0,0 +1,40 @@ +{ + "name": "webild-components", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview", + "typecheck": "tsc -b" + }, + "dependencies": { + "@tailwindcss/vite": "^4.2.2", + "clsx": "^2.1.1", + "embla-carousel": "^8.6.0", + "embla-carousel-react": "^8.6.0", + "motion": "^12.38.0", + "lucide-react": "^1.7.0", + "react": "^19.2.4", + "react-dom": "^19.2.4", + "react-router-dom": "^7.14.0", + "tailwind-merge": "^3.5.0", + "tailwindcss": "^4.2.2" + }, + "devDependencies": { + "@eslint/js": "^9.39.4", + "@types/node": "^24.12.2", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "eslint": "^9.39.4", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.5.2", + "globals": "^17.4.0", + "typescript": "~6.0.2", + "typescript-eslint": "^8.58.0", + "vite": "^8.0.4" + } +} \ No newline at end of file diff --git a/public/favicon.svg b/public/favicon.svg new file mode 100644 index 0000000..6893eb1 --- /dev/null +++ b/public/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/registry.json b/registry.json new file mode 100644 index 0000000..f9c682a --- /dev/null +++ b/registry.json @@ -0,0 +1,267 @@ +{ + "componentRegistry": { + "navbar": [ + { + "name": "NavbarCentered", + "import": "import NavbarCentered from '@/components/ui/NavbarCentered';", + "path": "@/components/ui/NavbarCentered", + "description": "Centered navigation bar with logo, links and buttons", + "propsSchema": { + "logo": "string", + "navItems": "{ name: string; href: string }[]", + "ctaButton": "{ text: string; href: string }" + } + } + ] + }, + "sectionRegistry": { + "hero": [ + { + "name": "HeroBillboard", + "import": "import HeroBillboard from '@/components/sections/hero/HeroBillboard';", + "path": "@/components/sections/hero/HeroBillboard", + "description": "Full-width centered hero with media below text content", + "propsSchema": { + "tag": "string", + "title": "string", + "description": "string", + "primaryButton": "{ text: string; href: string }", + "secondaryButton": "{ text: string; href: string }", + "imageSrc": "string \u2014 image URL (provide imageSrc OR videoSrc, not both)", + "videoSrc": "string \u2014 video URL (provide imageSrc OR videoSrc, not both)" + } + }, + { + "name": "HeroBillboardGallery", + "import": "import HeroBillboardGallery from '@/components/sections/hero/HeroBillboardGallery';", + "path": "@/components/sections/hero/HeroBillboardGallery", + "description": "Full-width centered hero with image gallery below", + "propsSchema": { + "tag": "string", + "title": "string", + "description": "string", + "primaryButton": "{ text: string; href: string }", + "secondaryButton": "{ text: string; href: string }", + "items": "({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never })[]" + } + }, + { + "name": "HeroSplit", + "import": "import HeroSplit from '@/components/sections/hero/HeroSplit';", + "path": "@/components/sections/hero/HeroSplit", + "description": "Split layout hero with text on left, media on right", + "propsSchema": { + "tag": "string", + "title": "string", + "description": "string", + "primaryButton": "{ text: string; href: string }", + "secondaryButton": "{ text: string; href: string }", + "imageSrc": "string \u2014 image URL (provide imageSrc OR videoSrc, not both)", + "videoSrc": "string \u2014 video URL (provide imageSrc OR videoSrc, not both)" + } + } + ], + "about": [ + { + "name": "AboutFeaturesSplit", + "import": "import AboutFeaturesSplit from '@/components/sections/about/AboutFeaturesSplit';", + "path": "@/components/sections/about/AboutFeaturesSplit", + "description": "About section with feature list on left, media on right", + "propsSchema": { + "tag": "string", + "title": "string", + "description": "string", + "primaryButton?": "{ text: string; href: string }", + "secondaryButton?": "{ text: string; href: string }", + "items": "{ icon: LucideIcon (import from lucide-react, pass as component reference e.g. icon={Star} not icon=\"Star\"); title: string; description: string }[]", + "imageSrc": "string \u2014 image URL (provide imageSrc OR videoSrc, not both)", + "videoSrc": "string \u2014 video URL (provide imageSrc OR videoSrc, not both)" + } + }, + { + "name": "AboutMediaOverlay", + "import": "import AboutMediaOverlay from '@/components/sections/about/AboutMediaOverlay';", + "path": "@/components/sections/about/AboutMediaOverlay", + "description": "About section with text overlaid on media background", + "propsSchema": { + "tag": "string", + "title": "string", + "description": "string", + "primaryButton?": "{ text: string; href: string }", + "secondaryButton?": "{ text: string; href: string }", + "imageSrc": "string \u2014 image URL (provide imageSrc OR videoSrc, not both)", + "videoSrc": "string \u2014 video URL (provide imageSrc OR videoSrc, not both)" + } + }, + { + "name": "AboutTestimonial", + "import": "import AboutTestimonial from '@/components/sections/about/AboutTestimonial';", + "path": "@/components/sections/about/AboutTestimonial", + "description": "Testimonial section with quote and author info", + "propsSchema": { + "tag": "string", + "quote": "string", + "author": "string", + "role": "string", + "imageSrc": "string \u2014 image URL (provide imageSrc OR videoSrc, not both)", + "videoSrc": "string \u2014 video URL (provide imageSrc OR videoSrc, not both)" + } + }, + { + "name": "AboutTextSplit", + "import": "import AboutTextSplit from '@/components/sections/about/AboutTextSplit';", + "path": "@/components/sections/about/AboutTextSplit", + "description": "Text-only about section with title on left, descriptions on right", + "propsSchema": { + "title": "string", + "descriptions": "string[]", + "primaryButton?": "{ text: string; href: string }", + "secondaryButton?": "{ text: string; href: string }" + } + } + ], + "features": [ + { + "name": "FeaturesMediaCards", + "import": "import FeaturesMediaCards from '@/components/sections/features/FeaturesMediaCards';", + "path": "@/components/sections/features/FeaturesMediaCards", + "description": "Features section with media cards in grid or carousel", + "propsSchema": { + "tag": "string", + "title": "string", + "description": "string", + "primaryButton?": "{ text: string; href: string }", + "secondaryButton?": "{ text: string; href: string }", + "items": "({ title: string; description: string } & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never }))[]" + } + }, + { + "name": "FeaturesAlternatingSplit", + "import": "import FeaturesAlternatingSplit from '@/components/sections/features/FeaturesAlternatingSplit';", + "path": "@/components/sections/features/FeaturesAlternatingSplit", + "description": "Features section with numbered alternating left/right split cards", + "propsSchema": { + "tag": "string", + "title": "string", + "description": "string", + "primaryButton?": "{ text: string; href: string }", + "secondaryButton?": "{ text: string; href: string }", + "items": "({ title: string; description: string; primaryButton?: { text: string; href: string }; secondaryButton?: { text: string; href: string } } & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never }))[]" + } + }, + { + "name": "FeaturesLabeledList", + "import": "import FeaturesLabeledList from '@/components/sections/features/FeaturesLabeledList';", + "path": "@/components/sections/features/FeaturesLabeledList", + "description": "Features section with large labels and bullet-separated item lists", + "propsSchema": { + "tag": "string", + "title": "string", + "description": "string", + "primaryButton?": "{ text: string; href: string }", + "secondaryButton?": "{ text: string; href: string }", + "items": "{ label: string; title: string; bullets: string[]; primaryButton: { text: string; href: string }; secondaryButton: { text: string; href: string } }[]" + } + }, + { + "name": "FeaturesComparison", + "import": "import FeaturesComparison from '@/components/sections/features/FeaturesComparison';", + "path": "@/components/sections/features/FeaturesComparison", + "description": "Features comparison section with negative and positive lists side by side", + "propsSchema": { + "tag": "string", + "title": "string", + "description": "string", + "primaryButton?": "{ text: string; href: string }", + "secondaryButton?": "{ text: string; href: string }", + "negativeItems": "string[]", + "positiveItems": "string[]" + } + }, + { + "name": "FeaturesDetailedSteps", + "import": "import FeaturesDetailedSteps from '@/components/sections/features/FeaturesDetailedSteps';", + "path": "@/components/sections/features/FeaturesDetailedSteps", + "description": "Features section with detailed step cards including tag, title, subtitle, description, and tilted media", + "propsSchema": { + "tag": "string", + "title": "string", + "description": "string", + "primaryButton?": "{ text: string; href: string }", + "secondaryButton?": "{ text: string; href: string }", + "steps": "({ tag: string; title: string; subtitle: string; description: string } & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never }))[]" + } + }, + { + "name": "FeaturesTaggedCards", + "import": "import FeaturesTaggedCards from '@/components/sections/features/FeaturesTaggedCards';", + "path": "@/components/sections/features/FeaturesTaggedCards", + "description": "Features section with media cards featuring tag overlay on image and content card below", + "propsSchema": { + "tag": "string", + "title": "string", + "description": "string", + "primaryButton?": "{ text: string; href: string }", + "secondaryButton?": "{ text: string; href: string }", + "items": "({ tag: string; title: string; description: string; primaryButton?: { text: string; href: string }; secondaryButton?: { text: string; href: string } } & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never }))[]" + } + }, + { + "name": "FeaturesArrowCards", + "import": "import FeaturesArrowCards from '@/components/sections/features/FeaturesArrowCards';", + "path": "@/components/sections/features/FeaturesArrowCards", + "description": "Features section with media cards featuring title, tags, and arrow icon for navigation", + "propsSchema": { + "tag": "string", + "title": "string", + "description": "string", + "primaryButton?": "{ text: string; href: string }", + "secondaryButton?": "{ text: string; href: string }", + "items": "({ title: string; tags: string[] } & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never }))[]" + } + }, + { + "name": "FeaturesDetailedCards", + "import": "import FeaturesDetailedCards from '@/components/sections/features/FeaturesDetailedCards';", + "path": "@/components/sections/features/FeaturesDetailedCards", + "description": "Features section with horizontal split cards featuring title, author, tags, description, and media", + "propsSchema": { + "tag": "string", + "title": "string", + "description": "string", + "primaryButton?": "{ text: string; href: string }", + "secondaryButton?": "{ text: string; href: string }", + "items": "({ title: string; description: string; tags: string[] } & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never }))[]" + } + }, + { + "name": "FeaturesDualMedia", + "import": "import FeaturesDualMedia from '@/components/sections/features/FeaturesDualMedia';", + "path": "@/components/sections/features/FeaturesDualMedia", + "description": "Features section with icon cards featuring two media items side by side", + "propsSchema": { + "tag": "string", + "title": "string", + "description": "string", + "primaryButton?": "{ text: string; href: string }", + "secondaryButton?": "{ text: string; href: string }", + "items": "{ icon: LucideIcon (import from lucide-react, pass as component reference e.g. icon={Star} not icon=\"Star\"); title: string; description: string; mediaItems: [({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never }), ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never })] }[]" + } + }, + { + "name": "FeaturesMediaCarousel", + "import": "import FeaturesMediaCarousel from '@/components/sections/features/FeaturesMediaCarousel';", + "path": "@/components/sections/features/FeaturesMediaCarousel", + "description": "Carousel of cards with media background and text overlay at bottom", + "propsSchema": { + "tag": "string", + "title": "string", + "description": "string", + "primaryButton?": "{ text: string; href: string }", + "secondaryButton?": "{ text: string; href: string }", + "items": "({ title: string; description: string; buttonIcon: LucideIcon (import from lucide-react, pass as component reference e.g. icon={Star} not icon=\"Star\"); buttonHref?: string; buttonOnClick?: () => void } & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never }))[]" + } + } + ] + } +} \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..4709674 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,94 @@ +import AboutTestimonial from '@/components/sections/about/AboutTestimonial'; +import FeaturesDetailedSteps from '@/components/sections/features/FeaturesDetailedSteps'; +import FeaturesTaggedCards from '@/components/sections/features/FeaturesTaggedCards'; +import HeroBillboard from '@/components/sections/hero/HeroBillboard'; +import NavbarCentered from '@/components/ui/NavbarCentered'; + +export default function App() { + return ( + <> + + +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ + ); +} diff --git a/src/components/sections/about/AboutFeaturesSplit.tsx b/src/components/sections/about/AboutFeaturesSplit.tsx new file mode 100644 index 0000000..2989481 --- /dev/null +++ b/src/components/sections/about/AboutFeaturesSplit.tsx @@ -0,0 +1,80 @@ +import type { LucideIcon } from "lucide-react"; +import Button from "@/components/ui/Button"; +import TextAnimation from "@/components/ui/TextAnimation"; +import ImageOrVideo from "@/components/ui/ImageOrVideo"; + +type AboutFeaturesSplitProps = { + tag: string; + title: string; + description: string; + primaryButton?: { text: string; href: string }; + secondaryButton?: { text: string; href: string }; + items: { icon: LucideIcon; title: string; description: string }[]; +} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never }); + +const AboutFeaturesSplit = ({ + tag, + title, + description, + primaryButton, + secondaryButton, + items, + imageSrc, + videoSrc, +}: AboutFeaturesSplitProps) => { + return ( +
+
+
+ {tag} + + + + + + {(primaryButton || secondaryButton) && ( +
+ {primaryButton &&
+ )} +
+ +
+
+ {items.map((item, index) => ( +
+
+
+ +
+

{item.title}

+

{item.description}

+
+ {index < items.length - 1 && ( +
+ )} +
+ ))} +
+ +
+ +
+
+
+
+ ); +}; + +export default AboutFeaturesSplit; diff --git a/src/components/sections/about/AboutMediaOverlay.tsx b/src/components/sections/about/AboutMediaOverlay.tsx new file mode 100644 index 0000000..757c771 --- /dev/null +++ b/src/components/sections/about/AboutMediaOverlay.tsx @@ -0,0 +1,70 @@ +import { motion } from "motion/react"; +import Button from "@/components/ui/Button"; +import TextAnimation from "@/components/ui/TextAnimation"; +import ImageOrVideo from "@/components/ui/ImageOrVideo"; + +type AboutMediaOverlayProps = { + tag: string; + title: string; + description: string; + primaryButton?: { text: string; href: string }; + secondaryButton?: { text: string; href: string }; +} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never }); + +const AboutMediaOverlay = ({ + tag, + title, + description, + primaryButton, + secondaryButton, + imageSrc, + videoSrc, +}: AboutMediaOverlayProps) => { + return ( +
+
+
+ +
+
+ +
+
+ + {tag} + + + + + + + {(primaryButton || secondaryButton) && ( +
+ {primaryButton &&
+ )} +
+
+
+
+ ); +}; + +export default AboutMediaOverlay; diff --git a/src/components/sections/about/AboutTestimonial.tsx b/src/components/sections/about/AboutTestimonial.tsx new file mode 100644 index 0000000..b349ccb --- /dev/null +++ b/src/components/sections/about/AboutTestimonial.tsx @@ -0,0 +1,61 @@ +import { motion } from "motion/react"; +import { Quote } from "lucide-react"; +import TextAnimation from "@/components/ui/TextAnimation"; +import ImageOrVideo from "@/components/ui/ImageOrVideo"; + +type AboutTestimonialProps = { + tag: string; + quote: string; + author: string; + role: string; +} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never }); + +const AboutTestimonial = ({ + tag, + quote, + author, + role, + imageSrc, + videoSrc, +}: AboutTestimonialProps) => { + return ( +
+
+
+
+ +
+ +
+ {tag} + + + +
+ {author} + + {role} +
+
+
+ + + + +
+
+ ); +}; + +export default AboutTestimonial; diff --git a/src/components/sections/about/AboutTextSplit.tsx b/src/components/sections/about/AboutTextSplit.tsx new file mode 100644 index 0000000..db44260 --- /dev/null +++ b/src/components/sections/about/AboutTextSplit.tsx @@ -0,0 +1,56 @@ +import Button from "@/components/ui/Button"; +import TextAnimation from "@/components/ui/TextAnimation"; + +interface AboutTextSplitProps { + title: string; + descriptions: string[]; + primaryButton?: { text: string; href: string }; + secondaryButton?: { text: string; href: string }; +} + +const AboutTextSplit = ({ + title, + descriptions, + primaryButton, + secondaryButton, +}: AboutTextSplitProps) => { + return ( +
+
+
+
+ +
+ +
+ {descriptions.map((desc, index) => ( + + ))} + + {(primaryButton || secondaryButton) && ( +
+ {primaryButton &&
+ )} +
+
+ +
+
+
+ ); +}; + +export default AboutTextSplit; diff --git a/src/components/sections/features/FeaturesAlternatingSplit.tsx b/src/components/sections/features/FeaturesAlternatingSplit.tsx new file mode 100644 index 0000000..817ff08 --- /dev/null +++ b/src/components/sections/features/FeaturesAlternatingSplit.tsx @@ -0,0 +1,93 @@ +import { motion } from "motion/react"; +import Button from "@/components/ui/Button"; +import TextAnimation from "@/components/ui/TextAnimation"; +import ImageOrVideo from "@/components/ui/ImageOrVideo"; +import { cls } from "@/lib/utils"; + +type FeatureItem = { + title: string; + description: string; + primaryButton?: { text: string; href: string }; + secondaryButton?: { text: string; href: string }; +} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never }); + +interface FeaturesAlternatingSplitProps { + tag: string; + title: string; + description: string; + primaryButton?: { text: string; href: string }; + secondaryButton?: { text: string; href: string }; + items: FeatureItem[]; +} + +const FeaturesAlternatingSplit = ({ + tag, + title, + description, + primaryButton, + secondaryButton, + items, +}: FeaturesAlternatingSplitProps) => { + return ( +
+
+
+ {tag} + + + + + + {(primaryButton || secondaryButton) && ( +
+ {primaryButton &&
+ )} +
+ +
+ {items.map((item, index) => ( + +
+ + {index + 1} + +

{item.title}

+

{item.description}

+ {(item.primaryButton || item.secondaryButton) && ( +
+ {item.primaryButton &&
+ )} +
+
+ +
+
+ ))} +
+
+
+ ); +}; + +export default FeaturesAlternatingSplit; diff --git a/src/components/sections/features/FeaturesArrowCards.tsx b/src/components/sections/features/FeaturesArrowCards.tsx new file mode 100644 index 0000000..69c1e12 --- /dev/null +++ b/src/components/sections/features/FeaturesArrowCards.tsx @@ -0,0 +1,90 @@ +import { motion } from "motion/react"; +import { ArrowRight } from "lucide-react"; +import Button from "@/components/ui/Button"; +import TextAnimation from "@/components/ui/TextAnimation"; +import ImageOrVideo from "@/components/ui/ImageOrVideo"; +import GridOrCarousel from "@/components/ui/GridOrCarousel"; + +type FeatureItem = { + title: string; + tags: string[]; +} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never }); + +interface FeaturesArrowCardsProps { + tag: string; + title: string; + description: string; + primaryButton?: { text: string; href: string }; + secondaryButton?: { text: string; href: string }; + items: FeatureItem[]; +} + +const FeaturesArrowCards = ({ + tag, + title, + description, + primaryButton, + secondaryButton, + items, +}: FeaturesArrowCardsProps) => { + return ( +
+
+
+ {tag} + + + + + + {(primaryButton || secondaryButton) && ( +
+ {primaryButton &&
+ )} +
+ + + + {items.map((item) => ( +
+
+ +
+
+

{item.title}

+
+
+ {item.tags.map((itemTag) => ( + {itemTag} + ))} +
+ +
+
+
+ ))} +
+
+
+
+ ); +}; + +export default FeaturesArrowCards; diff --git a/src/components/sections/features/FeaturesComparison.tsx b/src/components/sections/features/FeaturesComparison.tsx new file mode 100644 index 0000000..266e0f8 --- /dev/null +++ b/src/components/sections/features/FeaturesComparison.tsx @@ -0,0 +1,83 @@ +import { motion } from "motion/react"; +import { Check, X } from "lucide-react"; +import Button from "@/components/ui/Button"; +import TextAnimation from "@/components/ui/TextAnimation"; + +interface FeaturesComparisonProps { + tag: string; + title: string; + description: string; + primaryButton?: { text: string; href: string }; + secondaryButton?: { text: string; href: string }; + negativeItems: string[]; + positiveItems: string[]; +} + +const FeaturesComparison = ({ + tag, + title, + description, + primaryButton, + secondaryButton, + negativeItems, + positiveItems, +}: FeaturesComparisonProps) => { + return ( +
+
+
+ {tag} + + + + + + {(primaryButton || secondaryButton) && ( +
+ {primaryButton &&
+ )} +
+ + +
+ {negativeItems.map((item) => ( +
+ + {item} +
+ ))} +
+ +
+ {positiveItems.map((item) => ( +
+ + {item} +
+ ))} +
+
+
+
+ ); +}; + +export default FeaturesComparison; diff --git a/src/components/sections/features/FeaturesDetailedCards.tsx b/src/components/sections/features/FeaturesDetailedCards.tsx new file mode 100644 index 0000000..fd76f2c --- /dev/null +++ b/src/components/sections/features/FeaturesDetailedCards.tsx @@ -0,0 +1,94 @@ +import { motion } from "motion/react"; +import Button from "@/components/ui/Button"; +import TextAnimation from "@/components/ui/TextAnimation"; +import ImageOrVideo from "@/components/ui/ImageOrVideo"; + +type FeatureItem = { + title: string; + description: string; + tags: string[]; +} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never }); + +interface FeaturesDetailedCardsProps { + tag: string; + title: string; + description: string; + primaryButton?: { text: string; href: string }; + secondaryButton?: { text: string; href: string }; + items: FeatureItem[]; +} + +const FeaturesDetailedCards = ({ + tag, + title, + description, + primaryButton, + secondaryButton, + items, +}: FeaturesDetailedCardsProps) => { + return ( +
+
+
+ {tag} + + + + + + {(primaryButton || secondaryButton) && ( +
+ {primaryButton &&
+ )} +
+ +
+ {items.map((item) => ( + +
+

{item.title}

+ +
+
+ {item.tags.map((itemTag) => ( + {itemTag} + ))} +
+ +

+ {item.description} +

+
+
+ +
+ +
+
+ ))} +
+
+
+ ); +}; + +export default FeaturesDetailedCards; diff --git a/src/components/sections/features/FeaturesDetailedSteps.tsx b/src/components/sections/features/FeaturesDetailedSteps.tsx new file mode 100644 index 0000000..686250c --- /dev/null +++ b/src/components/sections/features/FeaturesDetailedSteps.tsx @@ -0,0 +1,97 @@ +import { motion } from "motion/react"; +import Button from "@/components/ui/Button"; +import TextAnimation from "@/components/ui/TextAnimation"; +import ImageOrVideo from "@/components/ui/ImageOrVideo"; +import { cls } from "@/lib/utils"; + +type StepItem = { + tag: string; + title: string; + subtitle: string; + description: string; +} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never }); + +interface FeaturesDetailedStepsProps { + tag: string; + title: string; + description: string; + primaryButton?: { text: string; href: string }; + secondaryButton?: { text: string; href: string }; + steps: StepItem[]; +} + +const FeaturesDetailedSteps = ({ + tag, + title, + description, + primaryButton, + secondaryButton, + steps, +}: FeaturesDetailedStepsProps) => { + return ( +
+
+
+ {tag} + + + + + + {(primaryButton || secondaryButton) && ( +
+ {primaryButton &&
+ )} +
+ +
+ {steps.map((step, index) => { + const stepNumber = String(index + 1).padStart(2, "0"); + return ( + +
+
+ {step.tag} +

{step.title}

+
+
+
+

{step.subtitle}

+

{step.description}

+
+
+
+ {stepNumber} +
+ +
+
+ + ); + })} +
+
+
+ ); +}; + +export default FeaturesDetailedSteps; diff --git a/src/components/sections/features/FeaturesDualMedia.tsx b/src/components/sections/features/FeaturesDualMedia.tsx new file mode 100644 index 0000000..ec5b421 --- /dev/null +++ b/src/components/sections/features/FeaturesDualMedia.tsx @@ -0,0 +1,99 @@ +import { motion } from "motion/react"; +import type { LucideIcon } from "lucide-react"; +import Button from "@/components/ui/Button"; +import TextAnimation from "@/components/ui/TextAnimation"; +import ImageOrVideo from "@/components/ui/ImageOrVideo"; +import GridOrCarousel from "@/components/ui/GridOrCarousel"; + +type FeatureItem = { + icon: LucideIcon; + title: string; + description: string; + mediaItems: [ + ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never }), + ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never }) + ]; +}; + +interface FeaturesDualMediaProps { + tag: string; + title: string; + description: string; + primaryButton?: { text: string; href: string }; + secondaryButton?: { text: string; href: string }; + items: FeatureItem[]; +} + +const FeaturesDualMedia = ({ + tag, + title, + description, + primaryButton, + secondaryButton, + items, +}: FeaturesDualMediaProps) => { + return ( +
+
+
+ {tag} + + + + + + {(primaryButton || secondaryButton) && ( +
+ {primaryButton &&
+ )} +
+ + + + {items.map((item) => { + const IconComponent = item.icon; + return ( +
+
+
+ +
+

{item.title}

+

{item.description}

+
+ +
+ {item.mediaItems.map((mediaItem, mediaIndex) => ( +
+ +
+ ))} +
+
+ ); + })} +
+
+
+
+ ); +}; + +export default FeaturesDualMedia; diff --git a/src/components/sections/features/FeaturesLabeledList.tsx b/src/components/sections/features/FeaturesLabeledList.tsx new file mode 100644 index 0000000..13f0593 --- /dev/null +++ b/src/components/sections/features/FeaturesLabeledList.tsx @@ -0,0 +1,100 @@ +import { Fragment } from "react"; +import { motion } from "motion/react"; +import Button from "@/components/ui/Button"; +import TextAnimation from "@/components/ui/TextAnimation"; + +type FeatureItem = { + label: string; + title: string; + bullets: string[]; + primaryButton: { text: string; href: string }; + secondaryButton: { text: string; href: string }; +}; + +interface FeaturesLabeledListProps { + tag: string; + title: string; + description: string; + primaryButton?: { text: string; href: string }; + secondaryButton?: { text: string; href: string }; + items: FeatureItem[]; +} + +const FeaturesLabeledList = ({ + tag, + title, + description, + primaryButton, + secondaryButton, + items, +}: FeaturesLabeledListProps) => { + return ( +
+
+
+ {tag} + + + + + + {(primaryButton || secondaryButton) && ( +
+ {primaryButton &&
+ )} +
+ +
+ {items.map((item) => ( + +
+

{item.label}

+
+ +
+ +
+

{item.title}

+ +
+ {item.bullets.map((text, index) => ( + + {text} + {index < item.bullets.length - 1 && } + + ))} +
+ +
+
+
+ + ))} +
+
+
+ ); +}; + +export default FeaturesLabeledList; diff --git a/src/components/sections/features/FeaturesMediaCards.tsx b/src/components/sections/features/FeaturesMediaCards.tsx new file mode 100644 index 0000000..659ca74 --- /dev/null +++ b/src/components/sections/features/FeaturesMediaCards.tsx @@ -0,0 +1,82 @@ +import { motion } from "motion/react"; +import Button from "@/components/ui/Button"; +import TextAnimation from "@/components/ui/TextAnimation"; +import ImageOrVideo from "@/components/ui/ImageOrVideo"; +import GridOrCarousel from "@/components/ui/GridOrCarousel"; + +type FeatureItem = { + title: string; + description: string; +} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never }); + +interface FeaturesMediaCardsProps { + tag: string; + title: string; + description: string; + primaryButton?: { text: string; href: string }; + secondaryButton?: { text: string; href: string }; + items: FeatureItem[]; +} + +const FeaturesMediaCards = ({ + tag, + title, + description, + primaryButton, + secondaryButton, + items, +}: FeaturesMediaCardsProps) => { + return ( +
+
+
+ {tag} + + + + + + {(primaryButton || secondaryButton) && ( +
+ {primaryButton &&
+ )} +
+ + + + {items.map((item) => ( +
+
+ +
+
+

{item.title}

+

{item.description}

+
+
+ ))} +
+
+
+
+ ); +}; + +export default FeaturesMediaCards; diff --git a/src/components/sections/features/FeaturesMediaCarousel.tsx b/src/components/sections/features/FeaturesMediaCarousel.tsx new file mode 100644 index 0000000..61014ab --- /dev/null +++ b/src/components/sections/features/FeaturesMediaCarousel.tsx @@ -0,0 +1,98 @@ +import type { LucideIcon } from "lucide-react"; +import TextAnimation from "@/components/ui/TextAnimation"; +import ImageOrVideo from "@/components/ui/ImageOrVideo"; +import LoopCarousel from "@/components/ui/LoopCarousel"; +import Button from "@/components/ui/Button"; +import { useButtonClick } from "@/hooks/useButtonClick"; + +type FeatureItem = { + title: string; + description: string; + buttonIcon: LucideIcon; + buttonHref?: string; + buttonOnClick?: () => void; +} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never }); + +interface FeaturesMediaCarouselProps { + tag: string; + title: string; + description: string; + primaryButton?: { text: string; href: string }; + secondaryButton?: { text: string; href: string }; + items: FeatureItem[]; +} + +const FeatureMediaCarouselCard = ({ item }: { item: FeatureItem }) => { + const handleClick = useButtonClick(item.buttonHref, item.buttonOnClick); + const Icon = item.buttonIcon; + + return ( +
+ +