5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
node_modules
|
||||
dist
|
||||
.DS_Store
|
||||
*.tar.gz
|
||||
.astro
|
||||
8
.sisyphus/boulder.json
Normal file
8
.sisyphus/boulder.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"active_plan": "/Users/nvirellia/Projects/ignis-website/.sisyphus/plans/content-refresh.md",
|
||||
"started_at": "2026-01-25T18:11:10.652Z",
|
||||
"session_ids": [
|
||||
"ses_409aab7c7ffeDJJAqWV5ZEQjro"
|
||||
],
|
||||
"plan_name": "content-refresh"
|
||||
}
|
||||
43
.sisyphus/notepads/content-refresh/learnings.md
Normal file
43
.sisyphus/notepads/content-refresh/learnings.md
Normal file
@@ -0,0 +1,43 @@
|
||||
### Clowder to Community/Network Replacement
|
||||
|
||||
- **Files Modified**: `src/components/HomePage.tsx`, `src/components/JoinPage.tsx`, `src/pages/join.astro`
|
||||
- **Changes Made**: Replaced all instances of "clowder" with "community" or "network" based on context.
|
||||
- "Join the Clowder" -> "Join the Network" (HomePage.tsx)
|
||||
- "Join our clowder" -> "Join our network" (HomePage.tsx)
|
||||
- "clowderMembers" -> "communityMembers" (JoinPage.tsx)
|
||||
- "A clowder is a group of cats. Join our community of" -> "A community is a group of people. Join our community of" (JoinPage.tsx)
|
||||
- "Clowder Members" -> "Community Members" (JoinPage.tsx)
|
||||
- "Welcome to the Clowder!" -> "Welcome to the Community!" (JoinPage.tsx)
|
||||
- "Join the Clowder Community" -> "Join the Community" (join.astro)
|
||||
- **Verification**: `grep -ri "clowder" src` returned no results, confirming all references were removed.
|
||||
|
||||
### PawPrint Icon Replacement
|
||||
|
||||
- **Files Modified**: `src/components/ZenApp.tsx`, `src/components/JoinPage.tsx`, `src/components/HomePage.tsx`, `src/components/AboutPage.tsx`
|
||||
- **Changes Made**: Replaced all instances of `PawPrint` icon with `Network` icon from `lucide-react`. This included updating both the import statements and the JSX component usages.
|
||||
- **Verification**: `grep -r "PawPrint" src` returned no results.
|
||||
- **Commit Strategy**: Changes were split into two atomic commits to adhere to the principle of small, focused commits:
|
||||
1. `refactor: replace PawPrint icon with Network icon in ZenApp and JoinPage`
|
||||
2. `refactor: replace PawPrint icon with Network icon in HomePage and AboutPage`
|
||||
|
||||
### Task 5.3: Neutralize HomePage.tsx (Cat text & Testimonials)
|
||||
|
||||
- Successfully replaced all specified cat-themed text with professional/tech equivalents.
|
||||
- Successfully updated the specified image alt text.
|
||||
- Encountered an issue with `edit` when `oldString` was not unique, resolved by providing more context to the `oldString` parameter.
|
||||
- Verified changes using `grep -riE "meow|purr|cat|kitten" src/components/HomePage.tsx`, confirming that only acceptable occurrences of "cat" (in image paths and an untargeted quote) remain.
|
||||
|
||||
### Neutralized remaining components (About, ZenApp, Join, Explore, Home)
|
||||
|
||||
**Changes Made:**
|
||||
|
||||
- Modified `src/components/AboutPage.tsx`, `src/components/ZenApp.tsx`, `src/components/JoinPage.tsx`, `src/components/ExplorePage.tsx`, and `src/components/HomePage.tsx`.
|
||||
- Replaced cat-themed text with professional/tech equivalents in all specified files.
|
||||
- Replaced "PawPrint" references with "Network" or "Cpu" where appropriate.
|
||||
- Maintained existing layout and animations.
|
||||
|
||||
**Verification:**
|
||||
|
||||
- Ran `grep -riE "PawPrint|meow|clowder|kitten" src` which returned no results, confirming the removal of specified cat-themed terms and icons.
|
||||
- All files were modified as expected.
|
||||
- The changes were committed with the message: "refactor: neutralize remaining cat-themed content across all components".
|
||||
20
.sisyphus/notepads/remove-sections/learnings.md
Normal file
20
.sisyphus/notepads/remove-sections/learnings.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# Learnings - remove-sections
|
||||
|
||||
## Conventions
|
||||
|
||||
- Target file: `src/components/HomePage.tsx`.
|
||||
- Components to remove: `Community`, `QuoteSection`.
|
||||
- Navigation items to remove: `community`, `wisdom`.
|
||||
|
||||
## Patterns
|
||||
|
||||
- `navLinks` array defines the top navigation.
|
||||
- `useEffect` scroll listener updates `activeSection` based on element visibility.
|
||||
- Successfully removed Community and QuoteSection components from src/components/HomePage.tsx.
|
||||
- Deleted both the usage in the Home component's return block and their respective component definitions.
|
||||
- Verified that the Home component's return structure remained intact and the Footer component was not affected.
|
||||
- Successfully removed 'community' and 'wisdom' from `navLinks` array.
|
||||
- Successfully removed 'community' and 'wisdom' from `sections` array in `useEffect` scroll handler.
|
||||
- Ensured `navLinks` and `sections` array are consistent.
|
||||
|
||||
No unused lucide-react imports found in src/components/HomePage.tsx after the removal of Community and QuoteSection. All imported icons (Heart, Sparkles, Menu, X, Network, Users, BookOpen, Compass, Star, Lock, CheckCircle) are still actively used within the file.
|
||||
167
.sisyphus/plans/content-refresh.md
Normal file
167
.sisyphus/plans/content-refresh.md
Normal file
@@ -0,0 +1,167 @@
|
||||
# Plan: Ignis Network Rebrand & Content Refresh
|
||||
|
||||
## Context
|
||||
|
||||
### Original Request
|
||||
|
||||
Refresh the website with "Ignis Network" branding, specific mission statement, and handle "@ignisnet". Explicit instruction to "not modify styles too much".
|
||||
|
||||
### Analysis Findings
|
||||
|
||||
- **Tech Stack**: Astro + React + Tailwind CSS.
|
||||
- **Current State**: "Purrfectly Zen" branding with heavy cat themes (text & icons).
|
||||
- **Scope**: "Purrfectly Zen" appears in ~14 files (Pages, Components, Metadata).
|
||||
- **Architecture**:
|
||||
- Landing: `src/pages/index.astro`
|
||||
- Components: `src/components/HomePage.tsx` (Hero, Footer)
|
||||
- Layout: `src/layouts/Layout.astro` (Global Meta)
|
||||
|
||||
---
|
||||
|
||||
## Work Objectives
|
||||
|
||||
### Core Objective
|
||||
|
||||
Rebrand the entire application from "Purrfectly Zen" to "Ignis Network", replacing cat-themed content with the provided "Digital Transformation" mission statement.
|
||||
|
||||
### Concrete Deliverables
|
||||
|
||||
- [ ] Updated Brand Name globally ("Ignis Network")
|
||||
- [ ] Updated Hero Section (Headline & Description)
|
||||
- [ ] Updated Footer (Added "@ignisnet" link)
|
||||
- [ ] Updated Metadata (Titles & Descriptions in Astro files)
|
||||
- [ ] Neutralized Thematic Elements (Replace Cat icons/text with Tech equivalents where appropriate)
|
||||
|
||||
### Definition of Done
|
||||
|
||||
- [ ] `grep -r "Purrfectly Zen" .` returns 0 results
|
||||
- [ ] Hero displays the new mission statement
|
||||
- [ ] Footer contains "@ignisnet"
|
||||
|
||||
### Must Have
|
||||
|
||||
- Exact mission statement provided by user.
|
||||
- Preservation of existing layout and CSS classes (Tailwind).
|
||||
|
||||
### Must NOT Have
|
||||
|
||||
- Major layout restructuring.
|
||||
- Changes to color palette (unless requested later).
|
||||
- Broken links or missing assets.
|
||||
|
||||
---
|
||||
|
||||
## Verification Strategy
|
||||
|
||||
### Manual QA (Primary)
|
||||
|
||||
Since this is a content/UI refresh, verification will be done via **static analysis** (grep/reading files) and **manual verification instructions**.
|
||||
|
||||
---
|
||||
|
||||
## Task Flow
|
||||
|
||||
```
|
||||
1. Global Rename -> 2. Layout & Meta -> 3. Hero Update -> 4. Footer Update -> 5. Theme Cleanup
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## TODOs
|
||||
|
||||
- [ ] 1. Global Brand Name Replacement
|
||||
**What to do**:
|
||||
- Perform a global search and replace of "Purrfectly Zen" to "Ignis Network".
|
||||
- Check `package.json`, `README.md`, `src/layouts/Layout.astro`, `src/pages/*.astro`, and `src/components/*.tsx`.
|
||||
- Ensure case sensitivity is handled (e.g., check for all caps if applicable).
|
||||
|
||||
**References**:
|
||||
- `src/pages/index.astro` - Page Title
|
||||
- `src/layouts/Layout.astro` - Meta Title template
|
||||
- `package.json` - Project metadata
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] `grep -r "Purrfectly Zen" src` returns no results.
|
||||
- [ ] `grep -r "Ignis Network" src` returns multiple results.
|
||||
|
||||
**Parallelizable**: NO (Base step)
|
||||
|
||||
- [ ] 2. Update Layout & Metadata
|
||||
**What to do**:
|
||||
- Update `src/layouts/Layout.astro` meta description to reflect the new mission (shortened version).
|
||||
- Update `src/pages/index.astro` meta content.
|
||||
- Short Mission for Meta: "Redefining the digital frontier with next-generation internet services and advanced AI."
|
||||
|
||||
**References**:
|
||||
- `src/layouts/Layout.astro`
|
||||
- `src/pages/index.astro`
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] `grep "Redefining the digital frontier" src/layouts/Layout.astro` returns match.
|
||||
|
||||
**Parallelizable**: YES (with 3, 4)
|
||||
|
||||
- [ ] 3. Refresh Hero Section (HomePage.tsx)
|
||||
**What to do**:
|
||||
- Modify `src/components/HomePage.tsx`.
|
||||
- **Headline**: Change "Let's find your inner Zen with a cat!" to "Ignis Network".
|
||||
- **Sub-headline/Description**: Replace with: "To redefine the digital frontier by fusing next-generation internet services with advanced AI and software solutions. Our mission is to accelerate digital transformation, transforming how the world connects, computes, and evolves through intelligent, data-driven technologies."
|
||||
- **Button/CTA**: Change "Find your zen" to "Get Started" or "Explore Solutions".
|
||||
|
||||
**References**:
|
||||
- `src/components/HomePage.tsx:Hero` section
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] Content check: File contains the exact mission statement text.
|
||||
- [ ] No residual "cat" references in the Hero section.
|
||||
|
||||
**Parallelizable**: YES (with 2, 4)
|
||||
|
||||
- [ ] 4. Update Footer & Navigation
|
||||
**What to do**:
|
||||
- Locate Footer in `src/components/HomePage.tsx` (or imported component).
|
||||
- Add "@ignisnet" as a social link (e.g., X/Twitter).
|
||||
- Ensure the link is clickable: `href="https://x.com/ignisnet"` (Assumed X based on handle format).
|
||||
- Update Copyright text to "© 2026 Ignis Network".
|
||||
|
||||
**References**:
|
||||
- `src/components/HomePage.tsx` - Footer section
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] Footer contains "@ignisnet" text.
|
||||
- [ ] Footer contains "Ignis Network" copyright.
|
||||
|
||||
**Parallelizable**: YES (with 2, 3)
|
||||
|
||||
- [ ] 5. Thematic Cleanup (Tech vs Cat)
|
||||
**What to do**:
|
||||
- Search for "cat", "kitten", "meow", "purr", "clowder" in `src/components/*.tsx`.
|
||||
- Replace with neutral/tech terms:
|
||||
- "clowder" -> "community" or "network"
|
||||
- "meow" -> "hello" or remove
|
||||
- "purr" -> "hum" or remove
|
||||
- **Icons**: If `PawPrint` icon is imported from `lucide-react`, swap it for `Network`, `Cpu`, or `Zap` to match "Ignis" (Fire/Tech).
|
||||
- _Constraint_: Do not break layout. Only swap if 1:1 replacement is possible.
|
||||
|
||||
**References**:
|
||||
- `src/components/HomePage.tsx`
|
||||
- `src/components/AboutPage.tsx`
|
||||
- `src/components/ZenApp.tsx`
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] `grep -r "meow" src` returns 0 results.
|
||||
- [ ] `grep -r "PawPrint" src` returns 0 results (if swapped).
|
||||
|
||||
**Parallelizable**: NO (Run last to catch stragglers)
|
||||
|
||||
---
|
||||
|
||||
## Success Criteria
|
||||
|
||||
### Final Checklist
|
||||
|
||||
- [ ] Branding is "Ignis Network" everywhere.
|
||||
- [ ] Hero Mission Statement matches user input exactly.
|
||||
- [ ] Footer includes @ignisnet.
|
||||
- [ ] No obvious "Cat" text remains in a "Tech" website.
|
||||
- [ ] Layout remains intact (no broken styles).
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Fauzira Alpiandi
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
92
README.md
Normal file
92
README.md
Normal file
@@ -0,0 +1,92 @@
|
||||
# 🐱 Ignis Network Template
|
||||
|
||||
[](#ignis-network)
|
||||
|
||||
> Find your zen. One breath, one paw print at a time.
|
||||
>
|
||||
> A cat-themed meditation and mindfulness web application designed to help you disconnect from noise and reconnect with what matters: peace, presence, and gentle purrs.
|
||||
|
||||
---
|
||||
|
||||
This project is a beautiful, interactive, and lightweight meditation app template built with **Astro**, **React**, and **Tailwind CSS**. It's designed for developers who want to jumpstart a mindfulness-focused project with a charming "cat-wisdom" aesthetic.
|
||||
|
||||
---
|
||||
|
||||
## ✨ Template Features
|
||||
|
||||
| Feature | Description |
|
||||
| --------------------------- | ----------------------------------------------------------- |
|
||||
| 🧘 **Guided Sessions** | Interactive timer with customizable breathing animations. |
|
||||
| 🌬️ **Breathing Techniques** | Pre-configured 4-4-4-4 and "Purring Breath" patterns. |
|
||||
| 📱 **Astro Powered** | Hybrid rendering for speed and SEO optimization. |
|
||||
| 🎨 **Design System** | Warm, soft color palette with "Fredoka" and "Nunito" fonts. |
|
||||
| ✨ **Micro-interactions** | Smooth animations powered by Framer Motion. |
|
||||
| 🆓 **Zero Backend** | Pure client-side logic with `localStorage` persistence. |
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Technical Stack
|
||||
|
||||
- **Framework:** [Astro 5](https://astro.build/)
|
||||
- **UI Library:** React (integrated via `@astrojs/react`)
|
||||
- **Styling:** Tailwind CSS (v4 integration)
|
||||
- **Animations:** Framer Motion
|
||||
- **Icons:** Lucide React
|
||||
- **Routing:** Built-in Astro file-based routing + client-side navigation
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Getting Started
|
||||
|
||||
### 1. Clone & Install
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
pnpm install # or npm install
|
||||
```
|
||||
|
||||
### 2. Development
|
||||
|
||||
```bash
|
||||
# Start the dev server
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Visit `http://localhost:5000` to see the magic.
|
||||
|
||||
### 3. Build & Deploy
|
||||
|
||||
```bash
|
||||
# Build for production
|
||||
npm run build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📁 Project Structure
|
||||
|
||||
```
|
||||
/src
|
||||
├── components/ # React components (HomePage, ZenApp, etc.)
|
||||
├── layouts/ # Astro layouts (Global styles & SEO)
|
||||
├── lib/ # Utilities
|
||||
└── pages/ # Astro routes
|
||||
/public
|
||||
└── images/ # Cat illustrations and portraits
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Customization Tips
|
||||
|
||||
- **Branding:** Modify `src/layouts/Layout.astro` to change the CSS variables (`--primary`, `--background`) and update global SEO tags.
|
||||
- **Content:** The main logic lives in `src/components/ZenApp.tsx`. You can easily add more breathing patterns or cat affirmations there.
|
||||
- **Assets:** Swap out the images in `public/images/` to change the theme from cats to anything else (dogs, nature, etc.).
|
||||
|
||||
---
|
||||
|
||||
## 📝 License
|
||||
|
||||
[MIT](LICENSE) — Built for the community. Feel free to use, modify, and share.
|
||||
|
||||
Built with ❤️ and purrs. 🐾
|
||||
13
astro.config.mjs
Normal file
13
astro.config.mjs
Normal file
@@ -0,0 +1,13 @@
|
||||
import { defineConfig } from 'astro/config';
|
||||
import react from '@astrojs/react';
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
|
||||
export default defineConfig({
|
||||
integrations: [react()],
|
||||
vite: { plugins: [tailwindcss()] },
|
||||
server: {
|
||||
host: '0.0.0.0',
|
||||
port: 5000,
|
||||
allowedHosts: true,
|
||||
},
|
||||
});
|
||||
53
package.json
Normal file
53
package.json
Normal file
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"name": "purrfectly-zen",
|
||||
"private": true,
|
||||
"description": "Find your zen. One breath, one paw print at a time.",
|
||||
"license": "MIT",
|
||||
"author": "Fauzira Alpiandi <fauziralpiandi.dev@gmail.com>",
|
||||
"scripts": {
|
||||
"astro": "astro",
|
||||
"build": "astro build",
|
||||
"dev": "astro dev",
|
||||
"format": "prettier --write .",
|
||||
"preview": "astro preview"
|
||||
},
|
||||
"prettier": {
|
||||
"bracketSpacing": true,
|
||||
"singleQuote": true,
|
||||
"plugins": [
|
||||
"prettier-plugin-astro",
|
||||
"prettier-plugin-tailwindcss"
|
||||
],
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.astro",
|
||||
"options": {
|
||||
"parser": "astro"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/react": "^4.4.2",
|
||||
"@icons-pack/react-simple-icons": "^13.8.0",
|
||||
"astro": "^5.16.9",
|
||||
"canvas-nest.js": "^2.0.4",
|
||||
"clsx": "^2.1.1",
|
||||
"framer-motion": "^12.26.2",
|
||||
"lucide-react": "^0.562.0",
|
||||
"react": "^19.2.3",
|
||||
"react-dom": "^19.2.3",
|
||||
"tailwind-merge": "^3.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/vite": "^4.1.18",
|
||||
"@types/node": "^25.0.8",
|
||||
"@types/react": "^19.2.8",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"prettier": "^3.8.0",
|
||||
"prettier-plugin-astro": "^0.14.1",
|
||||
"prettier-plugin-tailwindcss": "^0.7.2",
|
||||
"tailwindcss": "^4.1.18",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
4301
pnpm-lock.yaml
generated
Normal file
4301
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
BIN
public/images/logo.png
Normal file
BIN
public/images/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
117
src/components/AboutPage.tsx
Normal file
117
src/components/AboutPage.tsx
Normal file
@@ -0,0 +1,117 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import { ArrowLeft, Heart, Network } from 'lucide-react';
|
||||
|
||||
export default function About() {
|
||||
return (
|
||||
<div className="from-primary/5 via-background to-secondary/5 min-h-screen bg-gradient-to-br p-6">
|
||||
<div className="mx-auto max-w-3xl">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="mb-8"
|
||||
>
|
||||
<a href="/">
|
||||
<button
|
||||
data-testid="button-back-about"
|
||||
className="hover:bg-secondary/20 mb-4 rounded-full p-2 transition-colors"
|
||||
>
|
||||
<ArrowLeft className="h-5 w-5" />
|
||||
</button>
|
||||
</a>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.1 }}
|
||||
className="space-y-8"
|
||||
>
|
||||
<div className="space-y-4 text-center">
|
||||
<Network className="text-primary mx-auto h-12 w-12" />
|
||||
<h1 className="font-heading text-foreground text-4xl font-bold md:text-5xl">
|
||||
About Ignis Network
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div className="bg-card rounded-[2rem] border-none shadow-lg">
|
||||
<div className="space-y-6 p-8 md:p-12">
|
||||
<div className="space-y-4">
|
||||
<h2 className="font-heading text-foreground text-2xl font-bold">
|
||||
Our Mission
|
||||
</h2>
|
||||
<p className="text-muted-foreground text-lg leading-relaxed">
|
||||
At Ignis Network, we believe that innovation is driven by
|
||||
clarity and focus. We empower individuals and organizations to
|
||||
achieve their full potential through cutting-edge solutions
|
||||
and strategic insights.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<h2 className="font-heading text-foreground text-2xl font-bold">
|
||||
Our Philosophy
|
||||
</h2>
|
||||
<p className="text-muted-foreground text-lg leading-relaxed">
|
||||
Our philosophy centers on the principles of efficiency,
|
||||
precision, and continuous improvement. We strive to simplify
|
||||
complex challenges, optimize workflows, and deliver robust,
|
||||
scalable solutions that drive progress.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<h2 className="font-heading text-foreground text-2xl font-bold">
|
||||
About Our Name
|
||||
</h2>
|
||||
<p className="text-muted-foreground text-lg leading-relaxed">
|
||||
"Ignis" is Latin for "fire," symbolizing innovation, passion,
|
||||
and the spark of creation. We aim to ignite potential, fuel
|
||||
progress, and illuminate the path forward for our clients and
|
||||
partners. Our name reflects our commitment to bringing
|
||||
transformative energy and clarity to the digital landscape.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<h2 className="font-heading text-foreground text-2xl font-bold">
|
||||
What We Offer
|
||||
</h2>
|
||||
<ul className="space-y-3">
|
||||
{[
|
||||
'Interactive meditation timer with customizable sessions',
|
||||
'Breathing techniques & mindfulness exercises',
|
||||
'Daily affirmations & zen wisdom',
|
||||
'A collaborative network of innovators',
|
||||
].map((item, idx) => (
|
||||
<li key={idx} className="flex items-start gap-3">
|
||||
<Heart className="text-primary mt-1 h-5 w-5 flex-shrink-0 fill-current" />
|
||||
<span className="text-muted-foreground">{item}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="border-border/40 border-t pt-4">
|
||||
<p className="text-muted-foreground text-center italic">
|
||||
Ignite your potential. One insight, one connection at a time.
|
||||
✨
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-center">
|
||||
<a href="/">
|
||||
<button
|
||||
data-testid="button-back-home-about"
|
||||
className="bg-primary text-primary-foreground hover:bg-primary/90 rounded-full px-8 py-2 font-bold"
|
||||
>
|
||||
Back to Home
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
165
src/components/ContactPage.tsx
Normal file
165
src/components/ContactPage.tsx
Normal file
@@ -0,0 +1,165 @@
|
||||
import { useState } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { ArrowLeft, Mail, Heart } from 'lucide-react';
|
||||
|
||||
export default function Contact() {
|
||||
const [name, setName] = useState('');
|
||||
const [email, setEmail] = useState('');
|
||||
const [message, setMessage] = useState('');
|
||||
const [submitted, setSubmitted] = useState(false);
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (name && email && message) {
|
||||
const messages = JSON.parse(
|
||||
localStorage.getItem('contactMessages') || '[]',
|
||||
);
|
||||
messages.push({ name, email, message, date: new Date().toISOString() });
|
||||
localStorage.setItem('contactMessages', JSON.stringify(messages));
|
||||
setSubmitted(true);
|
||||
setName('');
|
||||
setEmail('');
|
||||
setMessage('');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="from-primary/5 via-background to-secondary/5 min-h-screen bg-gradient-to-br p-6">
|
||||
<div className="mx-auto max-w-2xl">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="mb-8"
|
||||
>
|
||||
<a href="/">
|
||||
<button
|
||||
data-testid="button-back-contact"
|
||||
className="hover:bg-secondary/20 mb-4 rounded-full p-2 transition-colors"
|
||||
>
|
||||
<ArrowLeft className="h-5 w-5" />
|
||||
</button>
|
||||
</a>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.1 }}
|
||||
className="space-y-8"
|
||||
>
|
||||
<div className="space-y-4 text-center">
|
||||
<Mail className="text-primary mx-auto h-12 w-12" />
|
||||
<h1 className="font-heading text-foreground text-4xl font-bold md:text-5xl">
|
||||
Get in Touch
|
||||
</h1>
|
||||
<p className="text-muted-foreground text-lg">
|
||||
Have questions or feedback? We'd love to hear from you!
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-card rounded-[2rem] border-none shadow-lg">
|
||||
<div className="p-8 md:p-12">
|
||||
{!submitted ? (
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
<div className="space-y-2">
|
||||
<label className="text-foreground text-sm font-bold">
|
||||
Name
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Your name"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
data-testid="input-contact-name"
|
||||
className="border-border bg-background focus:border-primary w-full rounded-lg border-2 px-4 py-3 transition-colors focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-foreground text-sm font-bold">
|
||||
Email
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
placeholder="your@email.com"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
data-testid="input-contact-email"
|
||||
className="border-border bg-background focus:border-primary w-full rounded-lg border-2 px-4 py-3 transition-colors focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-foreground text-sm font-bold">
|
||||
Message
|
||||
</label>
|
||||
<textarea
|
||||
placeholder="Your message..."
|
||||
value={message}
|
||||
onChange={(e) => setMessage(e.target.value)}
|
||||
data-testid="textarea-contact-message"
|
||||
rows={5}
|
||||
className="border-border bg-background focus:border-primary w-full resize-none rounded-lg border-2 px-4 py-3 transition-colors focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
>
|
||||
<button
|
||||
data-testid="button-contact-submit"
|
||||
type="submit"
|
||||
className="bg-primary text-primary-foreground hover:bg-primary/90 w-full rounded-lg py-3 font-bold"
|
||||
>
|
||||
Send Message
|
||||
</button>
|
||||
</motion.div>
|
||||
</form>
|
||||
) : (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
className="space-y-4 text-center"
|
||||
>
|
||||
<div className="mx-auto flex h-16 w-16 items-center justify-center rounded-full bg-green-100">
|
||||
<Heart className="h-8 w-8 fill-current text-green-600" />
|
||||
</div>
|
||||
<h2 className="font-heading text-foreground text-2xl font-bold">
|
||||
Thank you!
|
||||
</h2>
|
||||
<p className="text-muted-foreground">
|
||||
We received your message and will get back to you soon. Stay
|
||||
zen! 🐱
|
||||
</p>
|
||||
<motion.div
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
>
|
||||
<button
|
||||
onClick={() => setSubmitted(false)}
|
||||
className="border-primary text-primary hover:bg-primary/10 mt-4 rounded-lg border-2 px-6 py-2 font-bold transition-colors"
|
||||
>
|
||||
Send Another Message
|
||||
</button>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-center">
|
||||
<a href="/">
|
||||
<button
|
||||
data-testid="button-back-home-contact"
|
||||
className="border-primary text-primary hover:bg-primary/10 rounded-full border-2 px-8 py-2 font-bold transition-colors"
|
||||
>
|
||||
Back to Home
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
382
src/components/ExplorePage.tsx
Normal file
382
src/components/ExplorePage.tsx
Normal file
@@ -0,0 +1,382 @@
|
||||
import { useState } from 'react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { ArrowLeft, Lightbulb, Wind, Heart, Sparkles } from 'lucide-react';
|
||||
|
||||
type Category = 'tips' | 'breathing' | 'affirmations';
|
||||
|
||||
interface Guide {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
icon: React.ReactNode;
|
||||
color: string;
|
||||
}
|
||||
|
||||
const Explore = () => {
|
||||
const [selectedCategory, setSelectedCategory] = useState<Category | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
const categories: Record<Category, Guide[]> = {
|
||||
tips: [
|
||||
{
|
||||
id: 'tip-1',
|
||||
title: 'The Power of Silence',
|
||||
description:
|
||||
'Like a focused engineer, find clarity in quiet moments. Deep work is where innovation begins.',
|
||||
icon: <Sparkles className="h-6 w-6" />,
|
||||
color: 'bg-amber-100',
|
||||
},
|
||||
{
|
||||
id: 'tip-2',
|
||||
title: 'Slow Blinking',
|
||||
description:
|
||||
"Practice mindful pauses. It's a way of signaling calm to your nervous system and fostering trust in your decisions.",
|
||||
icon: <Heart className="h-6 w-6" />,
|
||||
color: 'bg-pink-100',
|
||||
},
|
||||
{
|
||||
id: 'tip-3',
|
||||
title: 'Mindful Stretching',
|
||||
description:
|
||||
'Integrate micro-breaks throughout your day. Short, mindful pauses enhance productivity and prevent burnout.',
|
||||
icon: <Wind className="h-6 w-6" />,
|
||||
color: 'bg-green-100',
|
||||
},
|
||||
{
|
||||
id: 'tip-4',
|
||||
title: 'Comfort is Key',
|
||||
description:
|
||||
'Find your cozy corner. A comfortable space makes meditation easier and more enjoyable.',
|
||||
icon: <Lightbulb className="h-6 w-6" />,
|
||||
color: 'bg-blue-100',
|
||||
},
|
||||
{
|
||||
id: 'tip-5',
|
||||
title: 'Focus on Your Breath',
|
||||
description:
|
||||
'Let your breath be your anchor. When your mind wanders, gently bring it back to your natural rhythm.',
|
||||
icon: <Wind className="h-6 w-6" />,
|
||||
color: 'bg-teal-100',
|
||||
},
|
||||
{
|
||||
id: 'tip-6',
|
||||
title: 'Practice Gratitude',
|
||||
description:
|
||||
"Start each session by noting 3 things you're grateful for. This shifts your mind toward positivity.",
|
||||
icon: <Heart className="h-6 w-6" />,
|
||||
color: 'bg-fuchsia-100',
|
||||
},
|
||||
{
|
||||
id: 'tip-7',
|
||||
title: 'Meditate in Nature',
|
||||
description:
|
||||
'Whenever possible, take your meditation practice outdoors. Nature amplifies the calming effect.',
|
||||
icon: <Sparkles className="h-6 w-6" />,
|
||||
color: 'bg-emerald-100',
|
||||
},
|
||||
{
|
||||
id: 'tip-8',
|
||||
title: 'Same Time, Every Day',
|
||||
description:
|
||||
'Establish a routine. Consistent practice helps train your mind for optimal performance and focus.',
|
||||
icon: <Lightbulb className="h-6 w-6" />,
|
||||
color: 'bg-sky-100',
|
||||
},
|
||||
],
|
||||
breathing: [
|
||||
{
|
||||
id: 'breath-1',
|
||||
title: '4-4-4-4 Breathing',
|
||||
description:
|
||||
'Inhale for 4, hold for 4, exhale for 4, hold for 4. A balanced rhythm for mental clarity.',
|
||||
icon: <Wind className="h-6 w-6" />,
|
||||
color: 'bg-cyan-100',
|
||||
},
|
||||
{
|
||||
id: 'breath-2',
|
||||
title: 'Deep Focus Breath',
|
||||
description:
|
||||
'Exhale with a controlled, steady release. Feel the rhythm stabilize your focus and calm your mind.',
|
||||
icon: <Heart className="h-6 w-6" />,
|
||||
color: 'bg-purple-100',
|
||||
},
|
||||
{
|
||||
id: 'breath-3',
|
||||
title: 'Box Breathing',
|
||||
description:
|
||||
'Equal parts: inhale, hold, exhale, pause. Simple yet powerful for instant calm.',
|
||||
icon: <Sparkles className="h-6 w-6" />,
|
||||
color: 'bg-rose-100',
|
||||
},
|
||||
{
|
||||
id: 'breath-4',
|
||||
title: 'Extended Exhale',
|
||||
description:
|
||||
"Make your exhale longer than your inhale. Signals to your body it's safe to relax.",
|
||||
icon: <Lightbulb className="h-6 w-6" />,
|
||||
color: 'bg-lime-100',
|
||||
},
|
||||
{
|
||||
id: 'breath-5',
|
||||
title: 'Alternate Nostril Breathing',
|
||||
description:
|
||||
'Nadi Shodhana: Balance your nervous system by breathing through alternating nostrils.',
|
||||
icon: <Wind className="h-6 w-6" />,
|
||||
color: 'bg-violet-100',
|
||||
},
|
||||
{
|
||||
id: 'breath-6',
|
||||
title: 'Belly Breathing',
|
||||
description:
|
||||
'Place your hand on your belly. Feel it rise and fall with each breath. This activates calm.',
|
||||
icon: <Heart className="h-6 w-6" />,
|
||||
color: 'bg-pink-100',
|
||||
},
|
||||
{
|
||||
id: 'breath-7',
|
||||
title: 'Coherent Breathing',
|
||||
description:
|
||||
'5 seconds in, 5 seconds out. Creates heart-brain coherence for deep relaxation.',
|
||||
icon: <Sparkles className="h-6 w-6" />,
|
||||
color: 'bg-amber-100',
|
||||
},
|
||||
{
|
||||
id: 'breath-8',
|
||||
title: "Lion's Breath",
|
||||
description:
|
||||
'Take a deep breath, then exhale forcefully while opening your mouth. Releases tension instantly.',
|
||||
icon: <Lightbulb className="h-6 w-6" />,
|
||||
color: 'bg-orange-100',
|
||||
},
|
||||
],
|
||||
affirmations: [
|
||||
{
|
||||
id: 'aff-1',
|
||||
title: 'I breathe in calm, I breathe out noise',
|
||||
description:
|
||||
'Like a resilient system, I adapt to change and optimize for the future.',
|
||||
icon: <Heart className="h-6 w-6" />,
|
||||
color: 'bg-red-100',
|
||||
},
|
||||
{
|
||||
id: 'aff-2',
|
||||
title: 'Slow is better than fast',
|
||||
description:
|
||||
'I embrace the philosophy of precision and deliberate action.',
|
||||
icon: <Sparkles className="h-6 w-6" />,
|
||||
color: 'bg-yellow-100',
|
||||
},
|
||||
{
|
||||
id: 'aff-3',
|
||||
title: 'I deserve rest and comfort',
|
||||
description:
|
||||
"Strategic rest is not idleness, it's essential. I optimize my energy for peak performance.",
|
||||
icon: <Lightbulb className="h-6 w-6" />,
|
||||
color: 'bg-indigo-100',
|
||||
},
|
||||
{
|
||||
id: 'aff-4',
|
||||
title: 'Every moment is a new beginning',
|
||||
description:
|
||||
'Like a robust algorithm, I adapt and find optimal solutions.',
|
||||
icon: <Wind className="h-6 w-6" />,
|
||||
color: 'bg-orange-100',
|
||||
},
|
||||
{
|
||||
id: 'aff-5',
|
||||
title: 'My mind is clear and peaceful',
|
||||
description: 'I let go of stress and embrace serenity in every breath.',
|
||||
icon: <Heart className="h-6 w-6" />,
|
||||
color: 'bg-rose-100',
|
||||
},
|
||||
{
|
||||
id: 'aff-6',
|
||||
title: 'Like a stable network, I radiate calm',
|
||||
description:
|
||||
'I treat myself with the same precision and care I apply to my work.',
|
||||
icon: <Sparkles className="h-6 w-6" />,
|
||||
color: 'bg-pink-100',
|
||||
},
|
||||
{
|
||||
id: 'aff-7',
|
||||
title: 'Challenges make me stronger',
|
||||
description:
|
||||
'Like a well-engineered solution, I navigate challenges with resilience.',
|
||||
icon: <Lightbulb className="h-6 w-6" />,
|
||||
color: 'bg-cyan-100',
|
||||
},
|
||||
{
|
||||
id: 'aff-8',
|
||||
title: 'My energy is positive and vibrant',
|
||||
description:
|
||||
'I radiate calm confidence and attract good things into my life.',
|
||||
icon: <Wind className="h-6 w-6" />,
|
||||
color: 'bg-lime-100',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const categoryLabels: Record<
|
||||
Category,
|
||||
{ name: string; icon: React.ReactNode }
|
||||
> = {
|
||||
tips: { name: 'Zen Tips', icon: <Lightbulb className="h-5 w-5" /> },
|
||||
breathing: {
|
||||
name: 'Breathing Techniques',
|
||||
icon: <Wind className="h-5 w-5" />,
|
||||
},
|
||||
affirmations: {
|
||||
name: 'Daily Affirmations',
|
||||
icon: <Heart className="h-5 w-5" />,
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="from-secondary/10 via-background to-primary/5 min-h-screen bg-gradient-to-br p-6">
|
||||
{/* Header */}
|
||||
<div className="mx-auto max-w-4xl">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="mb-8 flex items-center gap-4"
|
||||
>
|
||||
<a href="/">
|
||||
<button
|
||||
data-testid="button-back-explore"
|
||||
className="hover:bg-secondary/20 rounded-full p-2 transition-colors"
|
||||
>
|
||||
<ArrowLeft className="h-5 w-5" />
|
||||
</button>
|
||||
</a>
|
||||
<div>
|
||||
<h1 className="font-heading text-foreground text-4xl font-bold">
|
||||
Explore Zen
|
||||
</h1>
|
||||
<p className="text-muted-foreground">
|
||||
Deepen your practice with timeless wisdom
|
||||
</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Category Selection */}
|
||||
<AnimatePresence mode="wait">
|
||||
{!selectedCategory ? (
|
||||
<motion.div
|
||||
key="categories"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -20 }}
|
||||
className="grid grid-cols-1 gap-6 md:grid-cols-3"
|
||||
>
|
||||
{(Object.keys(categoryLabels) as Category[]).map(
|
||||
(category, idx) => (
|
||||
<motion.button
|
||||
key={category}
|
||||
onClick={() => setSelectedCategory(category)}
|
||||
whileHover={{ scale: 1.02 }}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: idx * 0.1 }}
|
||||
data-testid={`button-category-${category}`}
|
||||
className="group"
|
||||
>
|
||||
<div className="bg-card h-full overflow-hidden rounded-[2rem] border-none shadow-lg transition-all hover:shadow-xl">
|
||||
<div className="flex flex-col items-center gap-4 p-8 text-center">
|
||||
<div className="bg-primary/10 group-hover:bg-primary/20 text-primary flex h-16 w-16 items-center justify-center rounded-full transition-colors">
|
||||
{categoryLabels[category].icon}
|
||||
</div>
|
||||
<h3 className="font-heading text-foreground text-2xl font-bold">
|
||||
{categoryLabels[category].name}
|
||||
</h3>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
{category === 'tips' && 'Learn meditation wisdom'}
|
||||
{category === 'breathing' &&
|
||||
'Master breathing techniques'}
|
||||
{category === 'affirmations' && 'Empower your mind'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</motion.button>
|
||||
),
|
||||
)}
|
||||
</motion.div>
|
||||
) : (
|
||||
/* Content View */
|
||||
<motion.div
|
||||
key="content"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -20 }}
|
||||
className="space-y-6"
|
||||
>
|
||||
{/* Back Button */}
|
||||
<motion.button
|
||||
onClick={() => setSelectedCategory(null)}
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className="text-primary hover:text-primary/80 mb-4 flex items-center gap-2 transition-colors"
|
||||
>
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
Back to categories
|
||||
</motion.button>
|
||||
|
||||
{/* Header */}
|
||||
<div>
|
||||
<h2 className="font-heading text-foreground mb-2 text-3xl font-bold">
|
||||
{categoryLabels[selectedCategory].name}
|
||||
</h2>
|
||||
<p className="text-muted-foreground">
|
||||
{selectedCategory === 'tips' &&
|
||||
'Insights for peak performance and well-being'}
|
||||
{selectedCategory === 'breathing' &&
|
||||
'Techniques to calm your mind and body'}
|
||||
{selectedCategory === 'affirmations' &&
|
||||
'Affirmations to start your day with zen'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Items Grid */}
|
||||
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||
<AnimatePresence>
|
||||
{categories[selectedCategory].map((item, idx) => (
|
||||
<motion.div
|
||||
key={item.id}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -20 }}
|
||||
transition={{ delay: idx * 0.05 }}
|
||||
data-testid={`card-${selectedCategory}-${item.id}`}
|
||||
whileHover={{ y: -5 }}
|
||||
>
|
||||
<div className="bg-card h-full overflow-hidden rounded-[2rem] border-none shadow-lg transition-all hover:shadow-xl">
|
||||
<div className="flex h-full flex-col gap-4 p-8">
|
||||
<div
|
||||
className={`text-primary flex h-12 w-12 items-center justify-center rounded-full ${item.color}`}
|
||||
>
|
||||
{item.icon}
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-heading text-foreground mb-2 text-xl font-bold">
|
||||
{item.title}
|
||||
</h3>
|
||||
<p className="text-muted-foreground leading-relaxed">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Explore;
|
||||
156
src/components/FaqPage.tsx
Normal file
156
src/components/FaqPage.tsx
Normal file
@@ -0,0 +1,156 @@
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { ArrowLeft, ChevronDown } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
|
||||
const FAQ = () => {
|
||||
const [expandedId, setExpandedId] = useState<string | null>(null);
|
||||
|
||||
const faqs = [
|
||||
{
|
||||
id: 'faq-1',
|
||||
question: 'How long should I meditate each day?',
|
||||
answer:
|
||||
'Start with just 5 minutes daily. Even a short practice is powerful when consistent. As you progress, you can extend to 10, 20, or 30 minutes.',
|
||||
},
|
||||
{
|
||||
id: 'faq-2',
|
||||
question: "I can't stop my mind from wandering. Am I doing it wrong?",
|
||||
answer:
|
||||
"Not at all! Mind-wandering is completely normal. Meditation is noticing when your mind wanders and gently bringing it back—that's the practice itself.",
|
||||
},
|
||||
{
|
||||
id: 'faq-3',
|
||||
question: 'Best time of day to meditate?',
|
||||
answer:
|
||||
"Early morning (after waking) is ideal for most people. However, the best time is whenever you'll actually do it. Consistency matters more than timing.",
|
||||
},
|
||||
{
|
||||
id: 'faq-4',
|
||||
question: 'Do I need a special place to meditate?',
|
||||
answer:
|
||||
'No. While a quiet, comfortable space helps, meditation can happen anywhere—your bedroom, a park, even on the bus. Find what works for you.',
|
||||
},
|
||||
{
|
||||
id: 'faq-5',
|
||||
question: 'Is meditation religious? Can anyone do it?',
|
||||
answer:
|
||||
'Meditation has roots in many traditions, but modern mindfulness meditation is secular and evidence-based. Anyone can benefit, regardless of beliefs.',
|
||||
},
|
||||
{
|
||||
id: 'faq-6',
|
||||
question: 'How long before I see results?',
|
||||
answer:
|
||||
'Some benefits (like reduced stress) appear within days. Deeper changes take weeks or months. Trust the process and keep practicing.',
|
||||
},
|
||||
{
|
||||
id: 'faq-7',
|
||||
question: 'What if I fall asleep during meditation?',
|
||||
answer:
|
||||
"It's fine! Your body might need rest. If it happens frequently, try meditating earlier in the day or sitting upright instead of lying down.",
|
||||
},
|
||||
{
|
||||
id: 'faq-8',
|
||||
question: 'Can I meditate while walking?',
|
||||
answer:
|
||||
'Yes! Walking meditation is a powerful practice. Focus on the sensation of each step, your breath, and your surroundings as you walk slowly.',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="from-secondary/10 via-background to-primary/5 min-h-screen bg-gradient-to-br p-6">
|
||||
<div className="mx-auto max-w-3xl">
|
||||
{/* Header */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="mb-12 flex items-center gap-4"
|
||||
>
|
||||
<a href="/">
|
||||
<button className="hover:bg-secondary/20 rounded-full p-2 transition-colors">
|
||||
<ArrowLeft className="h-5 w-5" />
|
||||
</button>
|
||||
</a>
|
||||
<div>
|
||||
<h1 className="font-heading text-foreground text-4xl font-bold">
|
||||
FAQ
|
||||
</h1>
|
||||
<p className="text-muted-foreground">
|
||||
Answers to your meditation questions
|
||||
</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* FAQs */}
|
||||
<div className="space-y-3">
|
||||
{faqs.map((faq, idx) => (
|
||||
<motion.div
|
||||
key={faq.id}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ delay: idx * 0.05 }}
|
||||
data-testid={`faq-item-${faq.id}`}
|
||||
>
|
||||
<div className="border-border/40 bg-card overflow-hidden rounded-xl border shadow-lg transition-shadow hover:shadow-xl">
|
||||
<button
|
||||
onClick={() =>
|
||||
setExpandedId(expandedId === faq.id ? null : faq.id)
|
||||
}
|
||||
className="bg-card hover:bg-card/80 w-full p-6 text-left transition-colors"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<h3 className="font-heading text-foreground text-lg font-bold">
|
||||
{faq.question}
|
||||
</h3>
|
||||
<motion.div
|
||||
animate={{ rotate: expandedId === faq.id ? 180 : 0 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
className="text-primary flex-shrink-0"
|
||||
>
|
||||
<ChevronDown className="h-5 w-5" />
|
||||
</motion.div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<AnimatePresence>
|
||||
{expandedId === faq.id && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, height: 0 }}
|
||||
animate={{ opacity: 1, height: 'auto' }}
|
||||
exit={{ opacity: 0, height: 0 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
className="border-border/40 overflow-hidden border-t"
|
||||
>
|
||||
<p className="text-muted-foreground p-6 leading-relaxed">
|
||||
{faq.answer}
|
||||
</p>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Footer CTA */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
whileInView={{ opacity: 1 }}
|
||||
viewport={{ once: true }}
|
||||
className="bg-primary/10 mt-12 rounded-2xl p-8 text-center"
|
||||
>
|
||||
<p className="text-muted-foreground mb-4">
|
||||
Still have questions? We'd love to hear from you.
|
||||
</p>
|
||||
<a href="/contact">
|
||||
<button className="bg-primary text-primary-foreground hover:bg-primary/90 rounded-full px-6 py-2 font-bold shadow-lg transition-all hover:shadow-xl">
|
||||
Contact Us
|
||||
</button>
|
||||
</a>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FAQ;
|
||||
151
src/components/GuidePage.tsx
Normal file
151
src/components/GuidePage.tsx
Normal file
@@ -0,0 +1,151 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import { ArrowLeft, CheckCircle, Lightbulb, Heart } from 'lucide-react';
|
||||
|
||||
const Guide = () => {
|
||||
const steps = [
|
||||
{
|
||||
title: 'Why Meditate?',
|
||||
description:
|
||||
'Meditation reduces stress and anxiety, improves focus, and enhances emotional well-being. It literally rewires your brain for peace.',
|
||||
icon: <Heart className="h-6 w-6" />,
|
||||
benefits: [
|
||||
'Better sleep quality',
|
||||
'Reduced anxiety & stress',
|
||||
'Improved focus & clarity',
|
||||
'Emotional resilience',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'How to Start',
|
||||
description:
|
||||
"You don't need experience. Just find a comfortable spot, close your eyes, and follow your breath. That's it!",
|
||||
icon: <Lightbulb className="h-6 w-6" />,
|
||||
steps: [
|
||||
'Find a quiet, comfortable place',
|
||||
'Sit or lie down (whatever feels right)',
|
||||
'Close your eyes',
|
||||
'Focus on your natural breath',
|
||||
'When your mind wanders, gently bring it back',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Building Consistency',
|
||||
description:
|
||||
'The magic happens through regular practice. Start small and build from there.',
|
||||
icon: <CheckCircle className="h-6 w-6" />,
|
||||
tips: [
|
||||
'Start with just 5 minutes',
|
||||
'Same time every day (ideally morning)',
|
||||
'Track your sessions',
|
||||
'Be patient with yourself',
|
||||
'Celebrate small wins',
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="from-secondary/10 via-background to-primary/5 min-h-screen bg-gradient-to-br p-6">
|
||||
<div className="mx-auto max-w-3xl">
|
||||
{/* Header */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="mb-12 flex items-center gap-4"
|
||||
>
|
||||
<a href="/">
|
||||
<button className="hover:bg-secondary/20 rounded-full p-2 transition-colors">
|
||||
<ArrowLeft className="h-5 w-5" />
|
||||
</button>
|
||||
</a>
|
||||
<div>
|
||||
<h1 className="font-heading text-foreground text-4xl font-bold">
|
||||
Beginner's Guide
|
||||
</h1>
|
||||
<p className="text-muted-foreground">
|
||||
Start your meditation journey with us
|
||||
</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Steps */}
|
||||
<div className="space-y-8">
|
||||
{steps.map((step, idx) => (
|
||||
<motion.div
|
||||
key={idx}
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ delay: idx * 0.1 }}
|
||||
>
|
||||
<div className="bg-card overflow-hidden rounded-2xl border-none shadow-lg transition-shadow hover:shadow-xl">
|
||||
<div className="p-8">
|
||||
{/* Title */}
|
||||
<div className="mb-4 flex items-center gap-4">
|
||||
<div className="bg-primary/20 text-primary flex h-12 w-12 items-center justify-center rounded-full">
|
||||
{step.icon}
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="font-heading text-foreground text-2xl font-bold">
|
||||
{step.title}
|
||||
</h2>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
Step {idx + 1} of {steps.length}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
<p className="text-muted-foreground mb-6 leading-relaxed">
|
||||
{step.description}
|
||||
</p>
|
||||
|
||||
{/* Benefits/Steps/Tips */}
|
||||
<div className="space-y-2">
|
||||
{(step.benefits || step.steps || step.tips)?.map(
|
||||
(item, i) => (
|
||||
<div key={i} className="flex items-start gap-3">
|
||||
<CheckCircle className="text-primary mt-1 h-5 w-5 flex-shrink-0" />
|
||||
<span className="text-foreground/80">{item}</span>
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Call to Action */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
whileInView={{ opacity: 1 }}
|
||||
viewport={{ once: true }}
|
||||
className="bg-primary/10 mt-12 rounded-2xl p-8 text-center"
|
||||
>
|
||||
<h2 className="font-heading text-foreground mb-3 text-2xl font-bold">
|
||||
Ready to begin? 🐱
|
||||
</h2>
|
||||
<p className="text-muted-foreground mb-6">
|
||||
Start with our meditation timer and pick a breathing technique that
|
||||
resonates with you.
|
||||
</p>
|
||||
<div className="flex flex-wrap justify-center gap-4">
|
||||
<a href="/app">
|
||||
<button className="bg-primary text-primary-foreground hover:bg-primary/90 rounded-full px-6 py-2 font-bold shadow-lg transition-all hover:shadow-xl">
|
||||
Start Meditating
|
||||
</button>
|
||||
</a>
|
||||
<a href="/explore">
|
||||
<button className="border-primary text-primary hover:bg-primary/10 rounded-full border-2 px-6 py-2 font-bold transition-colors">
|
||||
Explore Techniques
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Guide;
|
||||
482
src/components/HomePage.tsx
Normal file
482
src/components/HomePage.tsx
Normal file
@@ -0,0 +1,482 @@
|
||||
import { SiGo, SiKubernetes, SiNixos, SiOpentelemetry, SiReact, SiTalos, SiTerraform, SiTypescript } from '@icons-pack/react-simple-icons';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import {
|
||||
Heart,
|
||||
Sparkles,
|
||||
Menu,
|
||||
Snowflake,
|
||||
X,
|
||||
Network,
|
||||
Users,
|
||||
BookOpen,
|
||||
Compass,
|
||||
Star,
|
||||
Lock,
|
||||
CheckCircle,
|
||||
Cpu,
|
||||
Bot,
|
||||
CalendarHeart,
|
||||
Flame,
|
||||
} from 'lucide-react';
|
||||
import { useRef,
|
||||
useState, useEffect } from 'react';
|
||||
import CanvasNest from 'canvas-nest.js';
|
||||
import { skipPartiallyEmittedExpressions } from 'typescript';
|
||||
|
||||
const Navigation = () => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [activeSection, setActiveSection] = useState('home');
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
const sections = ['home', 'features', 'cta'];
|
||||
let current = 'home';
|
||||
|
||||
for (const section of sections) {
|
||||
const element = document.getElementById(section);
|
||||
if (element) {
|
||||
const rect = element.getBoundingClientRect();
|
||||
if (rect.top <= 100) {
|
||||
current = section;
|
||||
}
|
||||
}
|
||||
}
|
||||
setActiveSection(current);
|
||||
};
|
||||
|
||||
window.addEventListener('scroll', handleScroll);
|
||||
return () => window.removeEventListener('scroll', handleScroll);
|
||||
}, []);
|
||||
|
||||
const navLinks = [] as const;
|
||||
|
||||
return (
|
||||
<nav className="bg-background/80 border-border/40 relative sticky top-0 z-50 flex w-full items-center justify-between border-b px-6 py-6 shadow-sm backdrop-blur-lg md:px-12">
|
||||
<a
|
||||
href="/"
|
||||
className="text-primary flex items-center gap-3 transition-opacity hover:opacity-80"
|
||||
>
|
||||
<img src="images/logo.png" alt="Ignis Logo" className="h-10" />
|
||||
</a>
|
||||
|
||||
<a href="mailto:contact@ignisnet.co">
|
||||
<button
|
||||
data-testid="button-join-nav"
|
||||
className="cursor-pointer bg-primary text-primary-background hover:bg-primary/90 rounded-full px-6 py-2 font-bold shadow-lg transition-all hover:scale-105 hover:shadow-xl"
|
||||
>
|
||||
Contact Us
|
||||
</button>
|
||||
</a>
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
|
||||
const Hero = () => {
|
||||
const bodyRef = useRef<HTMLDivElement>(null);
|
||||
useEffect(() => {
|
||||
if (!bodyRef.current) return;
|
||||
|
||||
const config = {
|
||||
color: '241,151,142',
|
||||
count: 88,
|
||||
opacity: 0.8,
|
||||
zIndex: 0,
|
||||
};
|
||||
|
||||
const cn = new CanvasNest(bodyRef.current, config);
|
||||
|
||||
return () => {
|
||||
cn.destroy();
|
||||
};
|
||||
}, [])
|
||||
return (
|
||||
<section
|
||||
id="home"
|
||||
ref={bodyRef}
|
||||
className="relative flex min-h-[90vh] flex-col items-center overflow-hidden px-6 py-12 md:flex-row md:px-12 lg:px-24"
|
||||
>
|
||||
{/* Decorative Blobs */}
|
||||
<div className="bg-primary/10 absolute top-[-10%] left-[-10%] -z-10 h-[500px] w-[500px] rounded-full blur-3xl" />
|
||||
<div className="bg-accent/30 absolute right-[-5%] bottom-[10%] -z-10 h-[400px] w-[400px] rounded-full blur-3xl" />
|
||||
|
||||
<div className="relative z-10 w-full space-y-8 md:w-1/2">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
>
|
||||
<div className="font-heading font-normal text-foreground mb-6 text-5xl leading-[1.1] md:text-7xl">
|
||||
Ignis Network
|
||||
</div>
|
||||
<p className="text-muted-foreground max-w-3xl text-lg leading-relaxed md:text-xl">
|
||||
We're a startup company that builds resilient digital infrastructure designed to endure, with AI applied where it meaningfully adds strength and insight.
|
||||
</p>
|
||||
<p className="text-muted-foreground max-w-2xl text-lg leading-relaxed md:text-xl mt-2">
|
||||
We focus on stability, clarity, and long-term reliability — creating systems that smartly carry what matters, support growth over time, and let everything else move forward with confidence.
|
||||
</p>
|
||||
|
||||
<motion.div
|
||||
className="flex flex-wrap gap-4 mt-6"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.6, delay: 0.3 }}
|
||||
>
|
||||
{[
|
||||
{ icon: SiNixos, label: 'Nix', color: 'text-[#5277C3]' },
|
||||
{ icon: SiKubernetes, label: 'Kubernetes', color: 'text-[#326CE5]' },
|
||||
{
|
||||
icon: SiTerraform,
|
||||
label: 'Terraform',
|
||||
color: 'text-[#844FBA]',
|
||||
},
|
||||
{ icon: SiOpentelemetry, label: 'OpenTelemetry', color: 'text-[#000000]' },
|
||||
{ icon: Bot, label: 'Ignis AI', color: 'text-[#F1978E]'}
|
||||
].map((badge, idx) => (
|
||||
<motion.div
|
||||
key={idx}
|
||||
initial={{ opacity: 0, x: -10 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: 0.4 + idx * 0.1 }}
|
||||
className="bg-secondary/30 border-border/40 flex items-center justify-center gap-2 rounded-full border px-3 py-2"
|
||||
>
|
||||
<badge.icon
|
||||
className={`h-4 w-4 flex-shrink-0 ${badge.color}`}
|
||||
/>
|
||||
<span className="text-foreground text-xs font-semibold">
|
||||
{badge.label}
|
||||
</span>
|
||||
</motion.div>
|
||||
))}
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
<div className='flex-1 flex justify-center items-center'>
|
||||
<Flame className='stroke-[#F1978E] size-96 stroke-[0.3px] opacity-70 mb-4 mr-12 hidden md:block' />
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
const FeatureCard = ({
|
||||
title,
|
||||
desc,
|
||||
img,
|
||||
delay,
|
||||
testId,
|
||||
}: {
|
||||
title: string;
|
||||
desc: string;
|
||||
img: string;
|
||||
delay: number;
|
||||
testId: string;
|
||||
}) => (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay }}
|
||||
whileHover={{ y: -10 }}
|
||||
data-testid={testId}
|
||||
>
|
||||
<div className="bg-card h-full overflow-hidden rounded-[2rem] border-none shadow-lg transition-shadow duration-300 hover:shadow-xl">
|
||||
<div className="flex h-full flex-col p-0">
|
||||
<div className="bg-secondary/30 flex h-48 items-center justify-center p-8">
|
||||
<motion.img
|
||||
src={img}
|
||||
alt={title}
|
||||
className="h-32 w-auto object-contain drop-shadow-md"
|
||||
whileHover={{ scale: 1.1 }}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-1 flex-col items-center p-8 text-center">
|
||||
<h3 className="font-heading text-foreground mb-3 text-2xl font-bold">
|
||||
{title}
|
||||
</h3>
|
||||
<p className="text-muted-foreground leading-relaxed">{desc}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
|
||||
const Features = () => {
|
||||
return (
|
||||
<section
|
||||
id="features"
|
||||
className="relative bg-white/50 px-6 py-24 md:px-12 lg:px-24"
|
||||
>
|
||||
<div className="mx-auto max-w-7xl">
|
||||
<div className="mb-16 space-y-4 text-center">
|
||||
<span className="font-hand text-primary text-xl">Our Vision</span>
|
||||
<h2 className="font-heading text-foreground text-4xl font-bold md:text-5xl">
|
||||
Pioneering Digital Transformation
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-8 md:grid-cols-3">
|
||||
<FeatureCard
|
||||
title="AI-Driven Insights"
|
||||
desc="Leverage advanced AI to unlock strategic opportunities and optimize decision-making."
|
||||
img="/images/focused_mind_illustration.png"
|
||||
delay={0.1}
|
||||
testId="card-feature-chill"
|
||||
/>
|
||||
<FeatureCard
|
||||
title="Seamless Transformation"
|
||||
desc="Accelerate your digital journey with agile methodologies and intelligent solutions."
|
||||
img="/images/dynamic_innovation_illustration.png"
|
||||
delay={0.2}
|
||||
testId="card-feature-playful"
|
||||
/>
|
||||
<FeatureCard
|
||||
title="Collaborative Ecosystem"
|
||||
desc="Foster continuous learning and a collaborative spirit for groundbreaking innovation."
|
||||
img="/images/holistic_growth_illustration.png"
|
||||
delay={0.3}
|
||||
testId="card-feature-nourishment"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export const Footer = () => {
|
||||
return (
|
||||
<footer className="from-secondary/5 via-background to-primary/5 border-border/40 relative border-t bg-gradient-to-br px-6 py-16">
|
||||
<div className="mx-auto max-w-7xl">
|
||||
<div className="grid grid-cols-1 gap-12 md:grid-cols-3 md:gap-8 lg:gap-12">
|
||||
{/* Left Column: Brand */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5 }}
|
||||
className="flex flex-col justify-center space-y-3"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<img src="/images/logo.png" alt="Ignis Logo" className="h-8" />
|
||||
</div>
|
||||
<p className="text-muted-foreground text-sm leading-relaxed">
|
||||
Smart infrastructure built for stability, longevity, and trust.
|
||||
</p>
|
||||
By
|
||||
<a
|
||||
href="https://bsky.app/profile/sne.moe"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-muted-foreground hover:text-primary w-fit text-sm transition-colors"
|
||||
>
|
||||
@AsaiNeko
|
||||
</a>
|
||||
</motion.div>
|
||||
|
||||
{/* Center Column: Links by Category */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: 0.1 }}
|
||||
className="space-y-6"
|
||||
>
|
||||
{/* Events */}
|
||||
<div className="space-y-3">
|
||||
<div className="text-primary flex items-center gap-2">
|
||||
<CalendarHeart className="h-5 w-5" />
|
||||
<span className="font-heading text-sm font-bold">Events</span>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 pl-7">
|
||||
<span
|
||||
data-testid="link-footer-guide"
|
||||
className="text-muted-foreground hover:text-primary w-fit text-sm transition-colors"
|
||||
>
|
||||
NixCN Conference
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Contact */}
|
||||
<div className="space-y-3">
|
||||
<div className="text-primary flex items-center gap-2">
|
||||
<Users className="h-5 w-5" />
|
||||
<span className="font-heading text-sm font-bold">
|
||||
Contact
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 pl-7">
|
||||
<a
|
||||
href="/contact"
|
||||
data-testid="link-footer-contact"
|
||||
className="text-muted-foreground hover:text-primary w-fit text-sm transition-colors"
|
||||
>
|
||||
Contact Us
|
||||
</a>
|
||||
{/*<a
|
||||
href="/about"
|
||||
data-testid="link-footer-about-community"
|
||||
className="text-muted-foreground hover:text-primary w-fit text-sm transition-colors"
|
||||
>
|
||||
About Us
|
||||
</a>*/}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Legal */}
|
||||
{/*<div className="space-y-3">
|
||||
<div className="text-primary flex items-center gap-2">
|
||||
<Lock className="h-5 w-5" />
|
||||
<span className="font-heading text-sm font-bold">Legal</span>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 pl-7">
|
||||
<a
|
||||
href="/privacy"
|
||||
data-testid="link-footer-privacy"
|
||||
className="text-muted-foreground hover:text-primary w-fit text-sm transition-colors"
|
||||
>
|
||||
Privacy Policy
|
||||
</a>
|
||||
</div>
|
||||
</div>*/}
|
||||
</motion.div>
|
||||
|
||||
{/* Right Column: CTA */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: 20 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: 0.2 }}
|
||||
className="flex flex-col justify-center space-y-4"
|
||||
>
|
||||
<div className="space-y-2">
|
||||
<h4 className="font-heading text-foreground font-bold">
|
||||
Get in touch
|
||||
</h4>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
Reach out whenever there’s something worth building together.
|
||||
</p>
|
||||
</div>
|
||||
<a href="mailto:contact@ignisnet.co" className="w-full">
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
data-testid="button-footer-cta"
|
||||
className="cursor-pointer bg-primary text-primary-foreground hover:bg-primary/90 w-full rounded-full px-6 py-3 font-bold shadow-lg transition-all hover:shadow-xl"
|
||||
>
|
||||
Contact Us
|
||||
</motion.button>
|
||||
</a>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* Divider */}
|
||||
<div className="border-border/40 my-8 border-t" />
|
||||
|
||||
{/* Bottom: Copyright */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
whileInView={{ opacity: 1 }}
|
||||
viewport={{ once: true }}
|
||||
className="text-muted-foreground/60 flex flex-col items-center justify-between gap-4 text-center text-xs md:flex-row"
|
||||
>
|
||||
<p>© 2026 Ignis Network, Co. All rights reserved.</p>
|
||||
<p className="flex items-center justify-center gap-1">
|
||||
Built with <Heart className="h-3 w-3 text-red-400" /> for the
|
||||
digital age
|
||||
</p>
|
||||
</motion.div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
};
|
||||
|
||||
function CTA() {
|
||||
<section id="cta" className="px-6 py-24">
|
||||
<div className="mx-auto max-w-6xl">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6 }}
|
||||
className="mb-12 space-y-4 text-center"
|
||||
>
|
||||
<h2 className="font-heading text-foreground text-4xl font-bold md:text-5xl">
|
||||
Ready to Ignite Innovation?
|
||||
</h2>
|
||||
<p className="text-muted-foreground mx-auto max-w-2xl text-lg">
|
||||
Start your transformation journey today.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-6 md:grid-cols-3">
|
||||
{[
|
||||
{
|
||||
id: 'cta-start-app',
|
||||
title: 'Deploy AI Solutions',
|
||||
description:
|
||||
'Implement scalable AI architectures tailored to your enterprise needs.',
|
||||
href: '/app',
|
||||
testId: 'button-cta-app',
|
||||
icon: <Cpu className="text-primary h-8 w-8" />,
|
||||
},
|
||||
{
|
||||
id: 'cta-guide',
|
||||
title: 'Strategic Consulting',
|
||||
description:
|
||||
'Expert guidance on digital transformation and technology roadmap planning.',
|
||||
href: '/guide',
|
||||
testId: 'button-cta-guide',
|
||||
icon: <BookOpen className="text-primary h-8 w-8" />,
|
||||
},
|
||||
{
|
||||
id: 'cta-explore',
|
||||
title: 'Explore Platform',
|
||||
description:
|
||||
'Discover our suite of intelligent tools designed to accelerate your growth.',
|
||||
href: '/explore',
|
||||
testId: 'button-cta-explore',
|
||||
icon: <Compass className="text-primary h-8 w-8" />,
|
||||
},
|
||||
].map((cta, idx) => (
|
||||
<motion.div
|
||||
key={cta.id}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ delay: idx * 0.1, duration: 0.5 }}
|
||||
whileHover={{ y: -8 }}
|
||||
className="bg-card flex h-full flex-col items-center gap-4 rounded-[2rem] p-8 text-center shadow-lg transition-shadow hover:shadow-xl"
|
||||
>
|
||||
<div className="bg-primary/15 flex h-16 w-16 items-center justify-center rounded-full">
|
||||
{cta.icon}
|
||||
</div>
|
||||
<h3 className="font-heading text-foreground text-2xl font-bold">
|
||||
{cta.title}
|
||||
</h3>
|
||||
<p className="text-muted-foreground flex-1">
|
||||
{cta.description}
|
||||
</p>
|
||||
<a href={cta.href}>
|
||||
<button
|
||||
data-testid={cta.testId}
|
||||
className="bg-primary text-primary-foreground hover:bg-primary/90 cursor-pointer rounded-full px-6 py-2 text-sm font-bold shadow-md transition-colors"
|
||||
>
|
||||
Get Started
|
||||
</button>
|
||||
</a>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="bg-background selection:bg-primary/20 selection:text-primary-foreground min-h-screen">
|
||||
<Navigation />
|
||||
<Hero />
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
306
src/components/JoinPage.tsx
Normal file
306
src/components/JoinPage.tsx
Normal file
@@ -0,0 +1,306 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { ArrowLeft, Mail, Heart, Users, Network } from 'lucide-react';
|
||||
|
||||
const Join = () => {
|
||||
const [email, setEmail] = useState('');
|
||||
const [joined, setJoined] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const [members, setMembers] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
const communityMembers = JSON.parse(
|
||||
localStorage.getItem('communityMembers') || '[]',
|
||||
);
|
||||
setMembers(communityMembers.length);
|
||||
}, []);
|
||||
|
||||
const handleJoin = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!email) {
|
||||
setError('Please enter your email');
|
||||
return;
|
||||
}
|
||||
if (!email.includes('@')) {
|
||||
setError('Please enter a valid email');
|
||||
return;
|
||||
}
|
||||
|
||||
// Save to localStorage
|
||||
const communityMembers = JSON.parse(
|
||||
localStorage.getItem('communityMembers') || '[]',
|
||||
);
|
||||
if (!communityMembers.includes(email)) {
|
||||
communityMembers.push(email);
|
||||
localStorage.setItem(
|
||||
'communityMembers',
|
||||
JSON.stringify(communityMembers),
|
||||
);
|
||||
setMembers(communityMembers.length);
|
||||
}
|
||||
|
||||
setJoined(true);
|
||||
setEmail('');
|
||||
setError('');
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="from-primary/10 via-background to-accent/10 flex min-h-screen flex-col bg-gradient-to-br p-6">
|
||||
{/* Header */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="mx-auto w-full max-w-2xl"
|
||||
>
|
||||
<a href="/">
|
||||
<button
|
||||
data-testid="button-back-join"
|
||||
className="hover:bg-secondary/20 mb-6 rounded-full p-2 transition-colors"
|
||||
>
|
||||
<ArrowLeft className="h-5 w-5" />
|
||||
</button>
|
||||
</a>
|
||||
</motion.div>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="mx-auto flex w-full max-w-2xl flex-1 items-center justify-center">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
className="w-full"
|
||||
>
|
||||
<div className="bg-card/95 overflow-hidden rounded-[2rem] border-none shadow-2xl backdrop-blur-xl">
|
||||
<div className="flex flex-col items-center gap-8 p-12 text-center md:p-16">
|
||||
{!joined ? (
|
||||
<>
|
||||
{/* Header */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.1 }}
|
||||
className="space-y-4"
|
||||
>
|
||||
<div className="flex justify-center">
|
||||
<div className="bg-primary/20 flex h-20 w-20 items-center justify-center rounded-full">
|
||||
<Network className="text-primary h-10 w-10" />
|
||||
</div>
|
||||
</div>
|
||||
<h1 className="font-heading text-foreground text-4xl font-bold md:text-5xl">
|
||||
Join the Network
|
||||
</h1>
|
||||
<p className="text-muted-foreground mx-auto max-w-md text-lg leading-relaxed">
|
||||
A network is a group of innovators. Join our network of
|
||||
visionaries and tech enthusiasts worldwide.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
{/* Benefits */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.2 }}
|
||||
className="grid w-full grid-cols-1 gap-4 md:grid-cols-3"
|
||||
>
|
||||
<div className="space-y-2">
|
||||
<Mail className="text-primary mx-auto h-6 w-6" />
|
||||
<p className="text-foreground text-sm font-bold">
|
||||
Weekly Tips
|
||||
</p>
|
||||
<p className="text-muted-foreground text-xs">
|
||||
Weekly zen tips and affirmations
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Heart className="text-primary mx-auto h-6 w-6" />
|
||||
<p className="text-foreground text-sm font-bold">
|
||||
Community
|
||||
</p>
|
||||
<p className="text-muted-foreground text-xs">
|
||||
Connect with our meditation community
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Users className="text-primary mx-auto h-6 w-6" />
|
||||
<p className="text-foreground text-sm font-bold">
|
||||
Exclusive
|
||||
</p>
|
||||
<p className="text-muted-foreground text-xs">
|
||||
Access member-only content
|
||||
</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Form */}
|
||||
<motion.form
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.3 }}
|
||||
onSubmit={handleJoin}
|
||||
className="w-full space-y-4"
|
||||
>
|
||||
<div className="flex flex-col gap-3 sm:flex-row">
|
||||
<input
|
||||
type="email"
|
||||
placeholder="your@email.com"
|
||||
value={email}
|
||||
onChange={(e) => {
|
||||
setEmail(e.target.value);
|
||||
setError('');
|
||||
}}
|
||||
data-testid="input-email-join"
|
||||
className="bg-secondary/20 focus:border-primary flex-1 rounded-full border-2 border-transparent px-6 py-4 text-lg transition-all focus:outline-none"
|
||||
/>
|
||||
<motion.div
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
>
|
||||
<button
|
||||
data-testid="button-join-submit"
|
||||
className="bg-primary text-primary-foreground hover:bg-primary/90 rounded-full px-8 py-3 font-bold shadow-lg"
|
||||
>
|
||||
Join Now
|
||||
</button>
|
||||
</motion.div>
|
||||
</div>
|
||||
{error && (
|
||||
<motion.p
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
className="text-sm font-medium text-red-500"
|
||||
>
|
||||
{error}
|
||||
</motion.p>
|
||||
)}
|
||||
</motion.form>
|
||||
|
||||
{/* Stats */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 0.4 }}
|
||||
className="border-border/40 w-full border-t pt-6"
|
||||
>
|
||||
<div className="flex items-center justify-center gap-6 text-sm">
|
||||
<div>
|
||||
<p className="text-foreground text-lg font-bold">
|
||||
{members}+
|
||||
</p>
|
||||
<p className="text-muted-foreground">
|
||||
Community Members
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-border/40 h-8 w-px" />
|
||||
<div>
|
||||
<p className="text-foreground text-lg font-bold">
|
||||
24/7
|
||||
</p>
|
||||
<p className="text-muted-foreground">
|
||||
Support Available
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Privacy note */}
|
||||
<motion.p
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 0.5 }}
|
||||
className="text-muted-foreground text-xs"
|
||||
>
|
||||
We respect your privacy. No spam, just insights. Ever. ✨
|
||||
</motion.p>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{/* Success State */}
|
||||
<motion.div
|
||||
key="success"
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ type: 'spring', stiffness: 300, damping: 20 }}
|
||||
className="w-full space-y-6"
|
||||
>
|
||||
<motion.div
|
||||
animate={{ rotate: 360 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
className="flex justify-center"
|
||||
>
|
||||
<div className="flex h-24 w-24 items-center justify-center rounded-full bg-green-100">
|
||||
<Heart className="h-12 w-12 fill-current text-green-600" />
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<h2 className="font-heading text-foreground text-3xl font-bold">
|
||||
Welcome to the Community!
|
||||
</h2>
|
||||
<p className="text-muted-foreground text-lg">
|
||||
You're now part of our innovative network. Check your
|
||||
email for exclusive content! 📧
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.3 }}
|
||||
className="space-y-3 pt-4"
|
||||
>
|
||||
<p className="text-foreground font-bold">What's next?</p>
|
||||
<div className="mx-auto max-w-sm space-y-2 text-left">
|
||||
<p className="flex items-start gap-2 text-sm">
|
||||
<span className="text-primary font-bold">✓</span>
|
||||
<span>
|
||||
Weekly meditation tips delivered to your inbox
|
||||
</span>
|
||||
</p>
|
||||
<p className="flex items-start gap-2 text-sm">
|
||||
<span className="text-primary font-bold">✓</span>
|
||||
<span>
|
||||
Exclusive breathing techniques & affirmations
|
||||
</span>
|
||||
</p>
|
||||
<p className="flex items-start gap-2 text-sm">
|
||||
<span className="text-primary font-bold">✓</span>
|
||||
<span>Join our growing community of zen-seekers</span>
|
||||
</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className="pt-4"
|
||||
>
|
||||
<a href="/app">
|
||||
<button
|
||||
data-testid="button-start-meditation"
|
||||
className="bg-primary text-primary-foreground hover:bg-primary/90 rounded-full px-8 py-3 font-bold"
|
||||
>
|
||||
Start Meditating Now
|
||||
</button>
|
||||
</a>
|
||||
</motion.div>
|
||||
|
||||
<a href="/" className="inline-block">
|
||||
<button
|
||||
data-testid="button-back-home-join"
|
||||
className="border-primary text-primary hover:bg-primary/10 rounded-full border-2 px-8 py-2 transition-colors"
|
||||
>
|
||||
Back to Home
|
||||
</button>
|
||||
</a>
|
||||
</motion.div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Join;
|
||||
112
src/components/PrivacyPage.tsx
Normal file
112
src/components/PrivacyPage.tsx
Normal file
@@ -0,0 +1,112 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import { ArrowLeft } from 'lucide-react';
|
||||
|
||||
export default function Privacy() {
|
||||
return (
|
||||
<div className="from-primary/5 via-background to-secondary/5 min-h-screen bg-gradient-to-br p-6">
|
||||
<div className="mx-auto max-w-3xl">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="mb-8"
|
||||
>
|
||||
<a href="/">
|
||||
<button
|
||||
data-testid="button-back-privacy"
|
||||
className="hover:bg-secondary/20 mb-4 rounded-full p-2 transition-colors"
|
||||
>
|
||||
<ArrowLeft className="h-5 w-5" />
|
||||
</button>
|
||||
</a>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.1 }}
|
||||
className="space-y-8"
|
||||
>
|
||||
<div>
|
||||
<h1 className="font-heading text-foreground mb-4 text-4xl font-bold md:text-5xl">
|
||||
Privacy Policy
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div className="bg-card rounded-[2rem] border-none shadow-lg">
|
||||
<div className="space-y-6 p-8 md:p-12">
|
||||
<div className="space-y-3">
|
||||
<h2 className="font-heading text-foreground text-xl font-bold">
|
||||
Data We Collect
|
||||
</h2>
|
||||
<p className="text-muted-foreground">
|
||||
We collect email addresses from users who choose to join our
|
||||
community. No payment information or sensitive data is
|
||||
required. Everything is stored locally in your browser using
|
||||
localStorage.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<h2 className="font-heading text-foreground text-xl font-bold">
|
||||
How We Use Your Data
|
||||
</h2>
|
||||
<p className="text-muted-foreground">
|
||||
Your email is used only for newsletter communications and
|
||||
community updates. We never sell or share your data with third
|
||||
parties. You can unsubscribe anytime.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<h2 className="font-heading text-foreground text-xl font-bold">
|
||||
Session Data
|
||||
</h2>
|
||||
<p className="text-muted-foreground">
|
||||
Your meditation sessions and progress are stored locally on
|
||||
your device. We do not track or collect information about your
|
||||
usage patterns. You have full control over your data.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<h2 className="font-heading text-foreground text-xl font-bold">
|
||||
Security
|
||||
</h2>
|
||||
<p className="text-muted-foreground">
|
||||
We take your privacy seriously. All data transmissions are
|
||||
encrypted. We follow best practices for web security and
|
||||
regularly update our systems.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<h2 className="font-heading text-foreground text-xl font-bold">
|
||||
Contact Us
|
||||
</h2>
|
||||
<p className="text-muted-foreground">
|
||||
If you have concerns about your privacy, please reach out
|
||||
through our contact page. We're here to help.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="border-border/40 text-muted-foreground border-t pt-4 text-sm">
|
||||
Last updated: {new Date().toLocaleDateString()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-center">
|
||||
<a href="/">
|
||||
<button
|
||||
data-testid="button-back-home-privacy"
|
||||
className="bg-primary text-primary-foreground hover:bg-primary/90 rounded-full px-8 py-2 font-bold"
|
||||
>
|
||||
Back to Home
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
1
src/env.d.ts
vendored
Normal file
1
src/env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="astro/client" />
|
||||
181
src/layouts/Layout.astro
Normal file
181
src/layouts/Layout.astro
Normal file
@@ -0,0 +1,181 @@
|
||||
---
|
||||
interface Props {
|
||||
title?: string;
|
||||
description?: string;
|
||||
canonicalPath?: string;
|
||||
noindex?: boolean;
|
||||
}
|
||||
|
||||
const {
|
||||
title = 'Ignis Network - Cat-Inspired Meditation & Mindfulness',
|
||||
description = 'Redefining the digital frontier with next-generation internet services and advanced AI.',
|
||||
canonicalPath = '',
|
||||
noindex = false,
|
||||
} = Astro.props;
|
||||
|
||||
const siteUrl = Astro.site?.href || '';
|
||||
const canonicalUrl = `${siteUrl.replace(/\/$/, '')}${canonicalPath}`;
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="description" content={description} />
|
||||
<meta name="theme-color" content="#E8A598" />
|
||||
{noindex && <meta name="robots" content="noindex, nofollow" />}
|
||||
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<link rel="canonical" href={canonicalUrl} />
|
||||
|
||||
<title>{title}</title>
|
||||
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Fredoka:wght@300..700&family=Nunito:ital,wght@0,200..1000;1,200..1000&family=Patrick+Hand&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<script
|
||||
type="application/ld+json"
|
||||
set:html={JSON.stringify({
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'WebApplication',
|
||||
name: 'Ignis Network',
|
||||
description: 'Cat-inspired meditation and mindfulness application',
|
||||
applicationCategory: 'HealthApplication',
|
||||
operatingSystem: 'Web',
|
||||
offers: {
|
||||
'@type': 'Offer',
|
||||
price: '0',
|
||||
priceCurrency: 'USD',
|
||||
},
|
||||
})}
|
||||
/>
|
||||
</head>
|
||||
<body>
|
||||
<slot />
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<style is:global>
|
||||
@import 'tailwindcss';
|
||||
|
||||
@custom-variant hover (&:hover);
|
||||
|
||||
@theme inline {
|
||||
--color-background: hsl(var(--background));
|
||||
--color-foreground: hsl(var(--foreground));
|
||||
--color-card: hsl(var(--card));
|
||||
--color-card-foreground: hsl(var(--card-foreground));
|
||||
--color-popover: hsl(var(--popover));
|
||||
--color-popover-foreground: hsl(var(--popover-foreground));
|
||||
--color-primary: hsl(var(--primary));
|
||||
--color-primary-foreground: hsl(var(--primary-foreground));
|
||||
--color-secondary: hsl(var(--secondary));
|
||||
--color-secondary-foreground: hsl(var(--secondary-foreground));
|
||||
--color-muted: hsl(var(--muted));
|
||||
--color-muted-foreground: hsl(var(--muted-foreground));
|
||||
--color-accent: hsl(var(--accent));
|
||||
--color-accent-foreground: hsl(var(--accent-foreground));
|
||||
--color-destructive: hsl(var(--destructive));
|
||||
--color-destructive-foreground: hsl(var(--destructive-foreground));
|
||||
--color-border: hsl(var(--border));
|
||||
--color-input: hsl(var(--input));
|
||||
--color-ring: hsl(var(--ring));
|
||||
|
||||
--radius-sm: 0.5rem;
|
||||
--radius-md: 0.75rem;
|
||||
--radius-lg: 1rem;
|
||||
--radius-xl: 1.5rem;
|
||||
|
||||
--font-sans: 'Nunito', sans-serif;
|
||||
--font-heading: 'Fredoka', sans-serif;
|
||||
--font-hand: 'Patrick Hand', cursive;
|
||||
}
|
||||
|
||||
:root {
|
||||
--background: 40 33% 96%;
|
||||
--foreground: 25 10% 30%;
|
||||
|
||||
--primary: 5 78% 75%;
|
||||
--primary-foreground: 25 10% 20%;
|
||||
|
||||
--secondary: 140 25% 85%;
|
||||
--secondary-foreground: 140 30% 25%;
|
||||
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 25 10% 30%;
|
||||
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 25 10% 30%;
|
||||
|
||||
--muted: 40 20% 90%;
|
||||
--muted-foreground: 25 10% 50%;
|
||||
|
||||
--accent: 45 80% 90%;
|
||||
--accent-foreground: 25 10% 30%;
|
||||
|
||||
--destructive: 0 84% 60%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
|
||||
--border: 40 20% 90%;
|
||||
--input: 40 20% 90%;
|
||||
--ring: 6 78% 75%;
|
||||
|
||||
--radius: 1rem;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
|
||||
body {
|
||||
@apply bg-background text-foreground font-sans antialiased;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
@apply font-heading font-semibold tracking-tight;
|
||||
}
|
||||
}
|
||||
|
||||
button,
|
||||
a,
|
||||
nav,
|
||||
[role='button'],
|
||||
.no-select {
|
||||
@apply select-none;
|
||||
}
|
||||
|
||||
[role='presentation'],
|
||||
.pointer-none,
|
||||
svg[aria-hidden='true'] {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.absolute.blur-3xl,
|
||||
[class*='blob'],
|
||||
[class*='pattern'] {
|
||||
@apply pointer-events-none;
|
||||
}
|
||||
|
||||
svg:not([role='img']) {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.font-hand {
|
||||
font-family: var(--font-hand);
|
||||
}
|
||||
</style>
|
||||
35
src/lib/canvas-nest.d.ts
vendored
Normal file
35
src/lib/canvas-nest.d.ts
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
declare module 'canvas-nest.js' {
|
||||
/**
|
||||
* CanvasNest 配置项接口
|
||||
*/
|
||||
export interface Config {
|
||||
/** 线条颜色,格式为 "R,G,B" */
|
||||
color?: string;
|
||||
/** 线条透明度 (0~1) */
|
||||
opacity?: number;
|
||||
/** 粒子数量 */
|
||||
count?: number;
|
||||
/** z-index 层级 */
|
||||
zIndex?: number;
|
||||
/** 仅在特定元素内渲染(如果不传则默认为 body) */
|
||||
element?: HTMLElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* CanvasNest 实例类
|
||||
*/
|
||||
class CanvasNest {
|
||||
/**
|
||||
* @param el 容器元素
|
||||
* @param config 配置项
|
||||
*/
|
||||
constructor(el: HTMLElement, config?: Config);
|
||||
|
||||
/**
|
||||
* 销毁实例,移除 canvas 元素
|
||||
*/
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
export default CanvasNest;
|
||||
}
|
||||
6
src/lib/utils.ts
Normal file
6
src/lib/utils.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { clsx, type ClassValue } from 'clsx';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
12
src/pages/about.astro
Normal file
12
src/pages/about.astro
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
import Layout from '../layouts/Layout.astro';
|
||||
import AboutPage from '../components/AboutPage';
|
||||
---
|
||||
|
||||
<Layout
|
||||
title="About Us - Ignis Network"
|
||||
description="Learn about our mission to help you find peace through cat-inspired meditation. Discover the team behind Ignis Network."
|
||||
canonicalPath="/about"
|
||||
>
|
||||
<AboutPage client:load />
|
||||
</Layout>
|
||||
12
src/pages/contact.astro
Normal file
12
src/pages/contact.astro
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
import Layout from '../layouts/Layout.astro';
|
||||
import ContactPage from '../components/ContactPage';
|
||||
---
|
||||
|
||||
<Layout
|
||||
title="Contact Us - Ignis Network"
|
||||
description="Have questions or feedback about Ignis Network? We'd love to hear from you. Reach out to our team."
|
||||
canonicalPath="/contact"
|
||||
>
|
||||
<ContactPage client:load />
|
||||
</Layout>
|
||||
12
src/pages/explore.astro
Normal file
12
src/pages/explore.astro
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
import Layout from '../layouts/Layout.astro';
|
||||
import ExplorePage from '../components/ExplorePage';
|
||||
---
|
||||
|
||||
<Layout
|
||||
title="Explore Zen Tips & Techniques - Ignis Network"
|
||||
description="Discover zen tips, breathing techniques, and daily affirmations to deepen your meditation practice. Cat-inspired wisdom for mindfulness."
|
||||
canonicalPath="/explore"
|
||||
>
|
||||
<ExplorePage client:load />
|
||||
</Layout>
|
||||
12
src/pages/faq.astro
Normal file
12
src/pages/faq.astro
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
import Layout from '../layouts/Layout.astro';
|
||||
import FaqPage from '../components/FaqPage';
|
||||
---
|
||||
|
||||
<Layout
|
||||
title="Frequently Asked Questions - Ignis Network"
|
||||
description="Find answers to common questions about meditation, mindfulness, and using Ignis Network for your wellness journey."
|
||||
canonicalPath="/faq"
|
||||
>
|
||||
<FaqPage client:load />
|
||||
</Layout>
|
||||
12
src/pages/guide.astro
Normal file
12
src/pages/guide.astro
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
import Layout from '../layouts/Layout.astro';
|
||||
import GuidePage from '../components/GuidePage';
|
||||
---
|
||||
|
||||
<Layout
|
||||
title="Beginner's Meditation Guide - Ignis Network"
|
||||
description="Start your meditation journey with our step-by-step guide for beginners. Learn the basics of mindfulness with cat-inspired techniques."
|
||||
canonicalPath="/guide"
|
||||
>
|
||||
<GuidePage client:load />
|
||||
</Layout>
|
||||
12
src/pages/index.astro
Normal file
12
src/pages/index.astro
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
import Layout from '../layouts/Layout.astro';
|
||||
import HomePage from '../components/HomePage';
|
||||
---
|
||||
|
||||
<Layout
|
||||
title="Ignis Network - Cat-Inspired Meditation & Mindfulness"
|
||||
description="Redefining the digital frontier with next-generation internet services and advanced AI."
|
||||
canonicalPath="/"
|
||||
>
|
||||
<HomePage client:only="react" />
|
||||
</Layout>
|
||||
12
src/pages/join.astro
Normal file
12
src/pages/join.astro
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
import Layout from '../layouts/Layout.astro';
|
||||
import JoinPage from '../components/JoinPage';
|
||||
---
|
||||
|
||||
<Layout
|
||||
title="Join the Community - Ignis Network"
|
||||
description="Join our community of zen-seekers and cat lovers worldwide. Connect with fellow meditators on their mindfulness journey."
|
||||
canonicalPath="/join"
|
||||
>
|
||||
<JoinPage client:load />
|
||||
</Layout>
|
||||
12
src/pages/privacy.astro
Normal file
12
src/pages/privacy.astro
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
import Layout from '../layouts/Layout.astro';
|
||||
import PrivacyPage from '../components/PrivacyPage';
|
||||
---
|
||||
|
||||
<Layout
|
||||
title="Privacy Policy - Ignis Network"
|
||||
description="Learn how Ignis Network handles your data and protects your privacy. Our commitment to keeping your information safe."
|
||||
canonicalPath="/privacy"
|
||||
>
|
||||
<PrivacyPage client:load />
|
||||
</Layout>
|
||||
15
tsconfig.json
Normal file
15
tsconfig.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"extends": "astro/tsconfigs/strict",
|
||||
"include": ["**/*.ts", "**/*.tsx", "src/**/*.astro"],
|
||||
"exclude": ["node_modules", "dist"],
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "react",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"],
|
||||
"@components/*": ["./src/components/*"],
|
||||
"@assets/*": ["./attached_assets/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user