diff --git a/astro/README.md b/astro/README.md
new file mode 100644
index 0000000..818cd7b
--- /dev/null
+++ b/astro/README.md
@@ -0,0 +1,280 @@
+# @zorihq/astro
+
+ZoriHQ Analytics SDK for Astro applications.
+
+## Installation
+
+```bash
+npm install @zorihq/astro
+# or
+pnpm add @zorihq/astro
+# or
+yarn add @zorihq/astro
+```
+
+## Quick Start
+
+### 1. Add the ZoriScript component
+
+Add the `ZoriScript` component to your layout or base template:
+
+```astro
+---
+// src/layouts/Layout.astro
+import { ZoriScript } from '@zorihq/astro/ZoriScript.astro';
+---
+
+
+
+
+
+
+
+
+
+```
+
+### 2. Track custom events
+
+Use the client-side utilities in your scripts:
+
+```astro
+---
+// src/pages/index.astro
+import Layout from '../layouts/Layout.astro';
+---
+
+
+
+
+
+
+```
+
+## Configuration
+
+The `ZoriScript` component accepts the following props:
+
+| Prop | Type | Default | Description |
+|------|------|---------|-------------|
+| `publishableKey` | `string` | **required** | Your ZoriHQ publishable key |
+| `baseUrl` | `string` | `undefined` | Custom API base URL |
+| `comebackThreshold` | `number` | `undefined` | Threshold in ms for detecting user comebacks |
+| `trackQuickSwitches` | `boolean` | `undefined` | Whether to track quick tab switches |
+| `autoTrackPageViews` | `boolean` | `true` | Whether to auto-track page views |
+
+### Example with all options
+
+```astro
+
+```
+
+## View Transitions Support
+
+The SDK automatically supports Astro's View Transitions. Page views are tracked on:
+- Initial page load
+- Navigation via View Transitions (`astro:page-load` event)
+
+No additional configuration is needed.
+
+## Client-Side API
+
+### track(eventName, properties?)
+
+Track a custom event:
+
+```typescript
+import { track } from '@zorihq/astro';
+
+await track('button_click', { button_id: 'cta-primary' });
+```
+
+### identify(userInfo)
+
+Identify a user:
+
+```typescript
+import { identify } from '@zorihq/astro';
+
+await identify({
+ app_id: 'user-123',
+ email: 'user@example.com',
+ full_name: 'John Doe',
+ plan: 'premium'
+});
+```
+
+### trackPageView(properties?)
+
+Manually track a page view (useful when auto-tracking is disabled):
+
+```typescript
+import { trackPageView } from '@zorihq/astro';
+
+await trackPageView({ custom_property: 'value' });
+```
+
+### getVisitorId()
+
+Get the current visitor ID:
+
+```typescript
+import { getVisitorId } from '@zorihq/astro';
+
+const visitorId = await getVisitorId();
+```
+
+### getSessionId()
+
+Get the current session ID:
+
+```typescript
+import { getSessionId } from '@zorihq/astro';
+
+const sessionId = getSessionId();
+```
+
+### setConsent(preferences)
+
+Set user consent preferences:
+
+```typescript
+import { setConsent } from '@zorihq/astro';
+
+setConsent({
+ analytics: true,
+ marketing: false
+});
+```
+
+### optOut()
+
+Opt the user out of all tracking:
+
+```typescript
+import { optOut } from '@zorihq/astro';
+
+optOut();
+```
+
+### hasConsent()
+
+Check if the user has given consent:
+
+```typescript
+import { hasConsent } from '@zorihq/astro';
+
+if (hasConsent()) {
+ // User has given consent
+}
+```
+
+### isInitialized()
+
+Check if ZoriHQ is initialized:
+
+```typescript
+import { isInitialized } from '@zorihq/astro';
+
+if (isInitialized()) {
+ // Script is fully loaded
+}
+```
+
+### waitForInit(timeout?)
+
+Wait for ZoriHQ to be initialized:
+
+```typescript
+import { waitForInit, track } from '@zorihq/astro';
+
+await waitForInit(5000); // Wait up to 5 seconds
+await track('initialized');
+```
+
+### createClickHandler(eventName?, properties?)
+
+Create a reusable click handler:
+
+```typescript
+import { createClickHandler } from '@zorihq/astro';
+
+const handler = createClickHandler('cta_click', { location: 'header' });
+document.getElementById('cta')?.addEventListener('click', handler);
+```
+
+## Using with Framework Components
+
+If you're using React, Vue, or Svelte components in your Astro project, you can use the client-side utilities within those components:
+
+### React Component Example
+
+```tsx
+// src/components/SignupButton.tsx
+import { track } from '@zorihq/astro';
+
+export default function SignupButton() {
+ const handleClick = () => {
+ track('signup_clicked', { component: 'react' });
+ };
+
+ return ;
+}
+```
+
+### Vue Component Example
+
+```vue
+
+
+
+
+
+
+```
+
+## TypeScript Support
+
+This package includes TypeScript definitions. All types are exported:
+
+```typescript
+import type { ZoriConfig, ConsentPreferences, UserInfo } from '@zorihq/astro';
+
+const config: ZoriConfig = {
+ publishableKey: 'your-key',
+ baseUrl: 'https://api.example.com'
+};
+
+const user: UserInfo = {
+ app_id: '123',
+ email: 'user@example.com'
+};
+```
+
+## SSR Considerations
+
+The client-side utilities (`track`, `identify`, etc.) are designed to run in the browser. When using them in SSR contexts, they will safely return without effect.
+
+For server-side tracking needs, consider using the tracking API directly from your server endpoints.
+
+## License
+
+MIT
diff --git a/astro/ZoriScript.astro b/astro/ZoriScript.astro
new file mode 100644
index 0000000..b86519d
--- /dev/null
+++ b/astro/ZoriScript.astro
@@ -0,0 +1,87 @@
+---
+/**
+ * ZoriScript - Astro component for ZoriHQ Analytics
+ *
+ * This component injects the ZoriHQ tracking script and optionally
+ * tracks page views automatically, including support for View Transitions.
+ */
+
+export interface Props {
+ /** Your ZoriHQ publishable key */
+ publishableKey: string;
+ /** Custom API base URL (optional) */
+ baseUrl?: string;
+ /** Threshold in ms for detecting user comebacks (optional) */
+ comebackThreshold?: number;
+ /** Whether to track quick tab switches (optional) */
+ trackQuickSwitches?: boolean;
+ /** Whether to auto-track page views (default: true) */
+ autoTrackPageViews?: boolean;
+}
+
+const {
+ publishableKey,
+ baseUrl,
+ comebackThreshold,
+ trackQuickSwitches,
+ autoTrackPageViews = true,
+} = Astro.props;
+---
+
+
+
+{autoTrackPageViews && (
+
+)}
diff --git a/astro/index.ts b/astro/index.ts
new file mode 100644
index 0000000..7920ecb
--- /dev/null
+++ b/astro/index.ts
@@ -0,0 +1,212 @@
+/**
+ * ZoriHQ Analytics for Astro
+ *
+ * Client-side utilities for tracking events, identifying users,
+ * and managing analytics in Astro applications.
+ */
+
+import type { ZoriConfig, ConsentPreferences, UserInfo, ZoriCoreAPI } from '@zorihq/types';
+
+// Re-export shared types for convenience
+export type { ZoriConfig, ConsentPreferences, UserInfo } from '@zorihq/types';
+
+// Extend Window interface for ZoriHQ
+declare global {
+ interface Window {
+ ZoriHQ: any;
+ __zoriInitialPageTracked?: boolean;
+ }
+}
+
+/**
+ * Get the ZoriHQ instance from window
+ */
+function getZori(): any {
+ if (typeof window === 'undefined') return null;
+ return window.ZoriHQ;
+}
+
+/**
+ * Track a custom event
+ * @param eventName - Name of the event to track
+ * @param properties - Optional properties to attach to the event
+ * @returns Promise resolving to true if tracking was successful
+ */
+export async function track(eventName: string, properties?: Record): Promise {
+ const zori = getZori();
+ if (!zori) return false;
+
+ if (typeof zori.track === 'function') {
+ return await zori.track(eventName, properties);
+ } else {
+ zori.push(['track', eventName, properties]);
+ return true;
+ }
+}
+
+/**
+ * Identify a user with their information
+ * @param userInfo - User information to associate with the visitor
+ * @returns Promise resolving to true if identification was successful
+ */
+export async function identify(userInfo: UserInfo): Promise {
+ const zori = getZori();
+ if (!zori) return false;
+
+ if (typeof zori.identify === 'function') {
+ return await zori.identify(userInfo);
+ } else {
+ zori.push(['identify', userInfo]);
+ return true;
+ }
+}
+
+/**
+ * Get the current visitor ID
+ * @returns Promise resolving to the visitor ID string
+ */
+export async function getVisitorId(): Promise {
+ const zori = getZori();
+ if (!zori) return '';
+
+ if (typeof zori.getVisitorId === 'function') {
+ return await zori.getVisitorId();
+ }
+
+ return new Promise((resolve) => {
+ zori.push(['getVisitorId', (id: string) => resolve(id)]);
+ });
+}
+
+/**
+ * Get the current session ID
+ * @returns The session ID or null if not available
+ */
+export function getSessionId(): string | null {
+ const zori = getZori();
+ if (!zori || typeof zori.getSessionId !== 'function') return null;
+ return zori.getSessionId();
+}
+
+/**
+ * Set user consent preferences
+ * @param preferences - Consent preferences object
+ * @returns true if consent was set successfully
+ */
+export function setConsent(preferences: ConsentPreferences): boolean {
+ const zori = getZori();
+ if (!zori) return false;
+
+ if (typeof zori.setConsent === 'function') {
+ return zori.setConsent(preferences);
+ } else {
+ zori.push(['setConsent', preferences]);
+ return true;
+ }
+}
+
+/**
+ * Opt out of all tracking
+ * @returns true if opt-out was successful
+ */
+export function optOut(): boolean {
+ const zori = getZori();
+ if (!zori) return false;
+
+ if (typeof zori.optOut === 'function') {
+ return zori.optOut();
+ } else {
+ zori.push(['optOut']);
+ return true;
+ }
+}
+
+/**
+ * Check if user has given consent
+ * @returns true if user has given consent
+ */
+export function hasConsent(): boolean {
+ const zori = getZori();
+ if (!zori || typeof zori.hasConsent !== 'function') return true;
+ return zori.hasConsent();
+}
+
+/**
+ * Check if ZoriHQ script is loaded and initialized
+ * @returns true if the script is fully initialized
+ */
+export function isInitialized(): boolean {
+ const zori = getZori();
+ return zori && typeof zori.track === 'function';
+}
+
+/**
+ * Wait for ZoriHQ to be initialized
+ * @param timeout - Maximum time to wait in ms (default: 5000)
+ * @returns Promise that resolves when initialized or rejects on timeout
+ */
+export function waitForInit(timeout: number = 5000): Promise {
+ return new Promise((resolve, reject) => {
+ if (isInitialized()) {
+ resolve();
+ return;
+ }
+
+ const startTime = Date.now();
+ const checkInterval = setInterval(() => {
+ if (isInitialized()) {
+ clearInterval(checkInterval);
+ resolve();
+ } else if (Date.now() - startTime > timeout) {
+ clearInterval(checkInterval);
+ reject(new Error('ZoriHQ initialization timeout'));
+ }
+ }, 100);
+ });
+}
+
+/**
+ * Track a page view manually
+ * Useful when auto-tracking is disabled or for custom page view tracking
+ * @param properties - Optional additional properties
+ */
+export async function trackPageView(properties?: Record): Promise {
+ return track('page_view', {
+ page_title: typeof document !== 'undefined' ? document.title : '',
+ page_path: typeof window !== 'undefined' ? window.location.pathname : '',
+ page_search: typeof window !== 'undefined' ? window.location.search : '',
+ page_hash: typeof window !== 'undefined' ? window.location.hash : '',
+ ...properties,
+ });
+}
+
+/**
+ * Create a click tracking handler
+ * @param eventName - Name of the event to track (default: 'click')
+ * @param properties - Properties to attach to the event
+ * @returns Click handler function
+ */
+export function createClickHandler(
+ eventName: string = 'click',
+ properties?: Record
+): (event: Event) => void {
+ return (event: Event) => {
+ track(eventName, properties);
+ };
+}
+
+/**
+ * ZoriHQ API object implementing ZoriCoreAPI interface
+ * Provides a unified API for all tracking operations
+ */
+export const zori: ZoriCoreAPI = {
+ track,
+ identify,
+ getVisitorId,
+ getSessionId,
+ setConsent,
+ optOut,
+ hasConsent,
+};
+
+export default zori;
diff --git a/astro/package.json b/astro/package.json
new file mode 100644
index 0000000..2f0040a
--- /dev/null
+++ b/astro/package.json
@@ -0,0 +1,49 @@
+{
+ "name": "@zorihq/astro",
+ "version": "1.0.0",
+ "description": "ZoriHQ Analytics for Astro",
+ "main": "index.ts",
+ "types": "index.ts",
+ "exports": {
+ ".": {
+ "import": "./index.ts",
+ "require": "./index.ts",
+ "types": "./index.ts"
+ },
+ "./ZoriScript.astro": "./ZoriScript.astro"
+ },
+ "files": [
+ "index.ts",
+ "ZoriScript.astro",
+ "README.md",
+ "package.json"
+ ],
+ "scripts": {
+ "build": "tsc",
+ "prepublishOnly": "echo 'Publishing source files'",
+ "test": "echo 'Tests not yet configured'"
+ },
+ "keywords": [
+ "analytics",
+ "astro",
+ "tracking",
+ "zorihq"
+ ],
+ "author": "ZoriHQ",
+ "license": "MIT",
+ "peerDependencies": {
+ "astro": ">=3.0.0"
+ },
+ "dependencies": {
+ "@zorihq/types": "^1.0.0"
+ },
+ "devDependencies": {
+ "astro": "^4.0.0",
+ "typescript": "^5.0.0"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/zorihq/script.git",
+ "directory": "astro"
+ }
+}
diff --git a/astro/tsconfig.json b/astro/tsconfig.json
new file mode 100644
index 0000000..3517570
--- /dev/null
+++ b/astro/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "module": "ESNext",
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "declaration": true,
+ "declarationMap": true,
+ "outDir": "./dist",
+ "rootDir": "./",
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "allowSyntheticDefaultImports": true
+ },
+ "include": ["index.ts"],
+ "exclude": ["node_modules", "dist"]
+}