Full Code of Open-Dev-Society/OpenStock for AI

main 08304b51a449 cached
97 files
442.2 KB
122.9k tokens
200 symbols
1 requests
Download .txt
Showing preview only (469K chars total). Download the full file or copy to clipboard to get everything.
Repository: Open-Dev-Society/OpenStock
Branch: main
Commit: 08304b51a449
Files: 97
Total size: 442.2 KB

Directory structure:
gitextract_k4znbyx3/

├── .github/
│   └── FUNDING.yml
├── .gitignore
├── .idea/
│   ├── .gitignore
│   ├── OpenStock.iml
│   ├── git_toolbox_prj.xml
│   ├── inspectionProfiles/
│   │   └── Project_Default.xml
│   ├── modules.xml
│   └── vcs.xml
├── API_DOCS.md
├── Dockerfile
├── LICENSE
├── README.md
├── app/
│   ├── (auth)/
│   │   ├── layout.tsx
│   │   ├── sign-in/
│   │   │   └── page.tsx
│   │   └── sign-up/
│   │       └── page.tsx
│   ├── (root)/
│   │   ├── about/
│   │   │   └── page.tsx
│   │   ├── api-docs/
│   │   │   └── page.tsx
│   │   ├── help/
│   │   │   └── page.tsx
│   │   ├── layout.tsx
│   │   ├── page.tsx
│   │   ├── stocks/
│   │   │   └── [symbol]/
│   │   │       └── page.tsx
│   │   ├── terms/
│   │   │   └── page.tsx
│   │   └── watchlist/
│   │       └── page.tsx
│   ├── api/
│   │   └── inngest/
│   │       └── route.ts
│   ├── globals.css
│   └── layout.tsx
├── components/
│   ├── DonatePopup.tsx
│   ├── Footer.tsx
│   ├── Header.tsx
│   ├── NavItems.tsx
│   ├── OpenDevSocietyBranding.tsx
│   ├── SearchCommand.tsx
│   ├── SirayBanner.tsx
│   ├── TradingViewWidget.tsx
│   ├── UserDropdown.tsx
│   ├── WatchlistButton.tsx
│   ├── forms/
│   │   ├── CountrySelectField.tsx
│   │   ├── FooterLink.tsx
│   │   ├── InputField.tsx
│   │   └── SelectField.tsx
│   ├── ui/
│   │   ├── avatar.tsx
│   │   ├── button.tsx
│   │   ├── command.tsx
│   │   ├── dialog.tsx
│   │   ├── dropdown-menu.tsx
│   │   ├── input.tsx
│   │   ├── label.tsx
│   │   ├── popover.tsx
│   │   ├── select.tsx
│   │   └── sonner.tsx
│   └── watchlist/
│       ├── AlertsPanel.tsx
│       ├── CreateAlertModal.tsx
│       ├── NewsGrid.tsx
│       ├── TradingViewWatchlist.tsx
│       ├── WatchlistManager.tsx
│       ├── WatchlistStockChip.tsx
│       └── WatchlistTable.tsx
├── components.json
├── database/
│   ├── models/
│   │   ├── alert.model.ts
│   │   └── watchlist.model.ts
│   └── mongoose.ts
├── docker-compose.yml
├── eslint.config.mjs
├── hooks/
│   ├── useDebounce.ts
│   └── useTradingViewWidget.tsx
├── lib/
│   ├── actions/
│   │   ├── alert.actions.ts
│   │   ├── auth.actions.ts
│   │   ├── finnhub.actions.ts
│   │   ├── user.actions.ts
│   │   └── watchlist.actions.ts
│   ├── better-auth/
│   │   └── auth.ts
│   ├── constants.ts
│   ├── inngest/
│   │   ├── client.ts
│   │   ├── functions.ts
│   │   └── prompts.ts
│   ├── kit.ts
│   ├── nodemailer/
│   │   ├── index.ts
│   │   └── templates.ts
│   └── utils.ts
├── middleware/
│   └── index.ts
├── next.config.ts
├── package.json
├── postcss.config.mjs
├── scripts/
│   ├── check-env.mjs
│   ├── check_db_name.js
│   ├── create-kit-tag.mjs
│   ├── inspect-user.mjs
│   ├── list-kit-forms.mjs
│   ├── migrate-users-to-kit.mjs
│   ├── resolve_srv.js
│   ├── seed-inactive-user.mjs
│   ├── test-db.mjs
│   ├── test-db.ts
│   ├── test-kit.mjs
│   └── verify-watchlist.mjs
├── tsconfig.json
└── types/
    └── global.d.ts

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms

github: [ravixalgorithm]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
buy_me_a_coffee: ravixalgorithm
thanks_dev: # Replace with a single thanks.dev username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']


================================================
FILE: .gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# env files (can opt-in for committing if needed)
.env*

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts


================================================
FILE: .idea/.gitignore
================================================
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml


================================================
FILE: .idea/OpenStock.iml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
  <component name="NewModuleRootManager">
    <content url="file://$MODULE_DIR$">
      <excludeFolder url="file://$MODULE_DIR$/.tmp" />
      <excludeFolder url="file://$MODULE_DIR$/temp" />
      <excludeFolder url="file://$MODULE_DIR$/tmp" />
    </content>
    <orderEntry type="inheritedJdk" />
    <orderEntry type="sourceFolder" forTests="false" />
  </component>
</module>

================================================
FILE: .idea/git_toolbox_prj.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="GitToolBoxProjectSettings">
    <option name="commitMessageIssueKeyValidationOverride">
      <BoolValueOverride>
        <option name="enabled" value="true" />
      </BoolValueOverride>
    </option>
    <option name="commitMessageValidationEnabledOverride">
      <BoolValueOverride>
        <option name="enabled" value="true" />
      </BoolValueOverride>
    </option>
  </component>
</project>

================================================
FILE: .idea/inspectionProfiles/Project_Default.xml
================================================
<component name="InspectionProjectProfileManager">
  <profile version="1.0">
    <option name="myName" value="Project Default" />
    <inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
  </profile>
</component>

================================================
FILE: .idea/modules.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="ProjectModuleManager">
    <modules>
      <module fileurl="file://$PROJECT_DIR$/.idea/OpenStock.iml" filepath="$PROJECT_DIR$/.idea/OpenStock.iml" />
    </modules>
  </component>
</project>

================================================
FILE: .idea/vcs.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="VcsDirectoryMappings">
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
  </component>
</project>

================================================
FILE: API_DOCS.md
================================================
<div align="center">
  <img src="public/assets/images/logo.png" alt="OpenStock Logo" width="120" />
  <h1>OpenStock API & Architecture</h1>
  
  <p>
    <b>Modern. Open. Resilient.</b>
  </p>

  <p>
    <img src="https://img.shields.io/badge/status-active-success?style=for-the-badge" alt="Status" />
    <img src="https://img.shields.io/badge/AI-Gemini%20%2B%20Siray-blueviolet?style=for-the-badge" alt="AI Stack" />
    <img src="https://img.shields.io/badge/license-AGPL--3.0-blue?style=for-the-badge" alt="License" />
  </p>
</div>

---

## 🏗️ Architecture Overview

OpenStock leverages a resilient event-driven architecture powered by **Inngest**. We prioritize uptime for our generative features by utilizing a multi-provider AI strategy.

### 🧠 Intelligent Model Routing

We don't rely on a single point of failure. Our AI infrastructure automatically routes around outages.

```mermaid
graph LR
    A[User Action / Cron] -->|Trigger| B(Inngest Function);
    B --> C{Primary Provider};
    C -->|Gemini 2.5 Flash Lite| D[Generate Content];
    C -.->|Error / Rate Limit| E{Fallback Provider};
    E -->|Siray.ai Ultra| D;
    D --> F[Email / Notification];
    
    style C fill:#20c997,stroke:#333,stroke-width:2px,color:black
    style E fill:#3b82f6,stroke:#333,stroke-width:2px,color:white
    style D fill:#fff,stroke:#333,stroke-width:2px,color:black
```

---

## 🤝 AI Partners

### Primary: Google Gemini
The workhorse of our generative content. Fast, efficient, and deeply integrated via Inngest.

### Fallback: Siray.ai
> [!IMPORTANT]
> **Zero Downtime Guarantee.**
> When Gemini wavers, **Siray.ai** takes over instantly. No user request is ever dropped.

<div align="center">
  <br/>
  <a href="https://www.siray.ai/">
    <img src="public/assets/icons/siray.svg" alt="Siray.ai Logo" width="180" />
  </a>
  <p><i>The robust infrastructure backing OpenStock.</i></p>
</div>

---

## ⚡ Serverless Functions (Inngest)

Our background jobs are defined in `lib/inngest/functions.ts`.

| ID | Type | Schedule/Trigger | Purpose |
| :--- | :--- | :--- | :--- |
| `sign-up-email` | 🔔 Event | `app/user.created` | **Personalized Onboarding.** Generates a custom welcome message based on user quiz results. |
| `weekly-news-summary` | ⏱️ Cron | `0 9 * * 1` (Mon 9AM) | **Market Intelligence.** Summarizes top financial news and broadcasts to all users via Kit. |
| `check-stock-alerts` | ⏱️ Cron | `*/5 * * * *` | **Real-time Monitoring.** Checks user price targets against live market data. |
| `check-inactive-users` | ⏱️ Cron | `0 10 * * *` | **Re-engagement.** Identifies dormant users (>30 days) and sends a "We miss you" nudge. |

---

## 🔌 API Integrations

<details>
<summary><b>📈 Stock Data: Finnhub</b></summary>
<br/>

*   **Base URL:** `https://finnhub.io/api/v1`
*   **Key Features:** Real-time quotes, technical indicators, market news.
*   **Auth:** `NEXT_PUBLIC_FINNHUB_API_KEY`

</details>

<details>
<summary><b>📧 Email & Marketing: Kit (ConvertKit)</b></summary>
<br/>

*   **Role:** High-volume user broadcasts and tag management.
*   **Key Endpoints:**
    *   `POST /v3/tags/{tag_id}/subscribe` (User Migration)
    *   `POST /v3/broadcasts` (Newsletters)
*   **Auth:** `KIT_API_KEY` • `KIT_API_SECRET`

</details>

<details>
<summary><b>🗄️ Database: MongoDB Atlas</b></summary>
<br/>

*   **Connection:** Standard URI (DNS SRV bypassed for maximum reliability).
*   **Collections:** `users`, `watchlists`, `alerts`.

</details>

---

<div align="center">
  <sub>Documentation © Open Dev Society. Built with ❤️ for the Open Source Community.</sub>
</div>


================================================
FILE: Dockerfile
================================================
# Use official Node.js 20 Alpine image as base
FROM node:20-alpine

# Set working directory
WORKDIR /app

# Copy package.json and package-lock.json to leverage Docker cache
COPY package*.json ./
# Uncomment the next line if you use pnpm and have pnpm-lock.yaml
# COPY pnpm-lock.yaml ./

# Install dependencies (choose npm or pnpm)
RUN npm install
# If using pnpm, replace with:
# RUN npm install -g pnpm && pnpm install

# Copy all project files
COPY . .

# Build the Next.js application
RUN npm run build
# Or if using pnpm:
# RUN pnpm run build

# Expose the port Next.js runs on
EXPOSE 3000

# Start the Next.js production server
CMD ["npm", "start"]
# Or if using pnpm:
# CMD ["pnpm", "start"]


================================================
FILE: LICENSE
================================================
                    GNU AFFERO GENERAL PUBLIC LICENSE
                       Version 3, 19 November 2007

 Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

                            Preamble

  The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.

  The licenses for most software and other practical works are designed
to take away your freedom to share and change the works.  By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.

  When we speak of free software, we are referring to freedom, not
price.  Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.

  Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.

  A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate.  Many developers of free software are heartened and
encouraged by the resulting cooperation.  However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.

  The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community.  It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server.  Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.

  An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals.  This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.

  The precise terms and conditions for copying, distribution and
modification follow.

                       TERMS AND CONDITIONS

  0. Definitions.

  "This License" refers to version 3 of the GNU Affero General Public License.

  "Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.

  "The Program" refers to any copyrightable work licensed under this
License.  Each licensee is addressed as "you".  "Licensees" and
"recipients" may be individuals or organizations.

  To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy.  The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.

  A "covered work" means either the unmodified Program or a work based
on the Program.

  To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy.  Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.

  To "convey" a work means any kind of propagation that enables other
parties to make or receive copies.  Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.

  An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License.  If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.

  1. Source Code.

  The "source code" for a work means the preferred form of the work
for making modifications to it.  "Object code" means any non-source
form of a work.

  A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.

  The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form.  A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.

  The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities.  However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work.  For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.

  The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.

  The Corresponding Source for a work in source code form is that
same work.

  2. Basic Permissions.

  All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met.  This License explicitly affirms your unlimited
permission to run the unmodified Program.  The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work.  This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.

  You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force.  You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright.  Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.

  Conveying under any other circumstances is permitted solely under
the conditions stated below.  Sublicensing is not allowed; section 10
makes it unnecessary.

  3. Protecting Users' Legal Rights From Anti-Circumvention Law.

  No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.

  When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.

  4. Conveying Verbatim Copies.

  You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.

  You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.

  5. Conveying Modified Source Versions.

  You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:

    a) The work must carry prominent notices stating that you modified
    it, and giving a relevant date.

    b) The work must carry prominent notices stating that it is
    released under this License and any conditions added under section
    7.  This requirement modifies the requirement in section 4 to
    "keep intact all notices".

    c) You must license the entire work, as a whole, under this
    License to anyone who comes into possession of a copy.  This
    License will therefore apply, along with any applicable section 7
    additional terms, to the whole of the work, and all its parts,
    regardless of how they are packaged.  This License gives no
    permission to license the work in any other way, but it does not
    invalidate such permission if you have separately received it.

    d) If the work has interactive user interfaces, each must display
    Appropriate Legal Notices; however, if the Program has interactive
    interfaces that do not display Appropriate Legal Notices, your
    work need not make them do so.

  A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit.  Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.

  6. Conveying Non-Source Forms.

  You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:

    a) Convey the object code in, or embodied in, a physical product
    (including a physical distribution medium), accompanied by the
    Corresponding Source fixed on a durable physical medium
    customarily used for software interchange.

    b) Convey the object code in, or embodied in, a physical product
    (including a physical distribution medium), accompanied by a
    written offer, valid for at least three years and valid for as
    long as you offer spare parts or customer support for that product
    model, to give anyone who possesses the object code either (1) a
    copy of the Corresponding Source for all the software in the
    product that is covered by this License, on a durable physical
    medium customarily used for software interchange, for a price no
    more than your reasonable cost of physically performing this
    conveying of source, or (2) access to copy the
    Corresponding Source from a network server at no charge.

    c) Convey individual copies of the object code with a copy of the
    written offer to provide the Corresponding Source.  This
    alternative is allowed only occasionally and noncommercially, and
    only if you received the object code with such an offer, in accord
    with subsection 6b.

    d) Convey the object code by offering access from a designated
    place (gratis or for a charge), and offer equivalent access to the
    Corresponding Source in the same way through the same place at no
    further charge.  You need not require recipients to copy the
    Corresponding Source along with the object code.  If the place to
    copy the object code is a network server, the Corresponding Source
    may be on a different server (operated by you or a third party)
    that supports equivalent copying facilities, provided you maintain
    clear directions next to the object code saying where to find the
    Corresponding Source.  Regardless of what server hosts the
    Corresponding Source, you remain obligated to ensure that it is
    available for as long as needed to satisfy these requirements.

    e) Convey the object code using peer-to-peer transmission, provided
    you inform other peers where the object code and Corresponding
    Source of the work are being offered to the general public at no
    charge under subsection 6d.

  A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.

  A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling.  In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage.  For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product.  A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.

  "Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source.  The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.

  If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information.  But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).

  The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed.  Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.

  Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.

  7. Additional Terms.

  "Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law.  If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.

  When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it.  (Additional permissions may be written to require their own
removal in certain cases when you modify the work.)  You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.

  Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:

    a) Disclaiming warranty or limiting liability differently from the
    terms of sections 15 and 16 of this License; or

    b) Requiring preservation of specified reasonable legal notices or
    author attributions in that material or in the Appropriate Legal
    Notices displayed by works containing it; or

    c) Prohibiting misrepresentation of the origin of that material, or
    requiring that modified versions of such material be marked in
    reasonable ways as different from the original version; or

    d) Limiting the use for publicity purposes of names of licensors or
    authors of the material; or

    e) Declining to grant rights under trademark law for use of some
    trade names, trademarks, or service marks; or

    f) Requiring indemnification of licensors and authors of that
    material by anyone who conveys the material (or modified versions of
    it) with contractual assumptions of liability to the recipient, for
    any liability that these contractual assumptions directly impose on
    those licensors and authors.

  All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10.  If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term.  If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.

  If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.

  Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.

  8. Termination.

  You may not propagate or modify a covered work except as expressly
provided under this License.  Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).

  However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.

  Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.

  Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License.  If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.

  9. Acceptance Not Required for Having Copies.

  You are not required to accept this License in order to receive or
run a copy of the Program.  Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance.  However,
nothing other than this License grants you permission to propagate or
modify any covered work.  These actions infringe copyright if you do
not accept this License.  Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.

  10. Automatic Licensing of Downstream Recipients.

  Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License.  You are not responsible
for enforcing compliance by third parties with this License.

  An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations.  If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.

  You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License.  For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.

  11. Patents.

  A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based.  The
work thus licensed is called the contributor's "contributor version".

  A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version.  For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.

  Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.

  In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement).  To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.

  If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients.  "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.

  If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.

  A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License.  You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.

  Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.

  12. No Surrender of Others' Freedom.

  If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all.  For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.

  13. Remote Network Interaction; Use with the GNU General Public License.

  Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software.  This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.

  Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work.  The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.

  14. Revised Versions of this License.

  The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time.  Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.

  Each version is given a distinguishing version number.  If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation.  If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.

  If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.

  Later license versions may give you additional or different
permissions.  However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.

  15. Disclaimer of Warranty.

  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.

  16. Limitation of Liability.

  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.

  17. Interpretation of Sections 15 and 16.

  If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.

                     END OF TERMS AND CONDITIONS

            How to Apply These Terms to Your New Programs

  If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.

  To do so, attach the following notices to the program.  It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

    <one line to give the program's name and a brief idea of what it does.>
    Copyright (C) <year>  <name of author>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published
    by the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.

Also add information on how to contact you by electronic and paper mail.

  If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source.  For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code.  There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.

  You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<https://www.gnu.org/licenses/>.


================================================
FILE: README.md
================================================
<div align="center">
  Checkout new amazing projects also, <a href="github.com/open-dev-society/openreadme" target="_blank">OpenReadme </a> is live
</div>  
<a href="https://hellogithub.com/repository/Open-Dev-Society/OpenStock" target="_blank"><img src="https://abroad.hellogithub.com/v1/widgets/recommend.svg?rid=5c4337a9e2dd4a8ba8aba87a88f04b8b&claim_uid=07HezcXv9puSGKQ&theme=neutral" alt="Featured|HelloGitHub" style="width: 250px; height: 54px;" width="250" height="54" /></a>
<a href="https://peerlist.io/ravixalgorithm/project/openstock" target="_blank" rel="noreferrer">
				<img
					src="https://peerlist.io/api/v1/projects/embed/PRJH8OED7MBL9MGB9HRMKAKLM66KNN?showUpvote=true&theme=light"
					alt="OpenStock"
					style="width: auto; height: 54px;"
				/>
			</a>
<div align="center">
  <br />
  <a href="#" target="_blank">
    <img src="./public/assets/images/dashboard.png" alt="Project Banner" />
  </a>
  © Open Dev Society. This project is licensed under AGPL-3.0; if you modify, redistribute, or deploy it (including as a web service), you must release your source code under the same license and credit the original authors.
  <br />
  <br/>

  <div>
    <img src="https://img.shields.io/badge/-Next.js-black?style=for-the-badge&logoColor=white&logo=next.js&color=000000" alt="Next.js badge" />
    <img src="https://img.shields.io/badge/-TypeScript-black?style=for-the-badge&logoColor=white&logo=typescript&color=3178C6"/>
    <img src="https://img.shields.io/badge/-Tailwind%20CSS-black?style=for-the-badge&logoColor=white&logo=tailwindcss&color=38B2AC"/>
    <img src="https://img.shields.io/badge/-shadcn/ui-black?style=for-the-badge&logoColor=white&logo=shadcnui&color=000000"/>
    <img src="https://img.shields.io/badge/-Radix%20UI-black?style=for-the-badge&logoColor=white&logo=radixui&color=000000"/>
    <img src="https://img.shields.io/badge/-Better%20Auth-black?style=for-the-badge&logoColor=white&logo=betterauth&color=000000"/>
    <img src="https://img.shields.io/badge/-MongoDB-black?style=for-the-badge&logoColor=white&logo=mongodb&color=00A35C"/>
    <img src="https://img.shields.io/badge/-Inngest-black?style=for-the-badge&logoColor=white&logo=inngest&color=000000"/>
    <img src="https://img.shields.io/badge/-Nodemailer-black?style=for-the-badge&logoColor=white&logo=gmail&color=EA4335"/>
    <img src="https://img.shields.io/badge/-TradingView-black?style=for-the-badge&logoColor=white&logo=tradingview&color=2962FF"/>
    <img src="https://img.shields.io/badge/-Finnhub-black?style=for-the-badge&logoColor=white&color=30B27A"/>
    <img src="https://img.shields.io/badge/-CodeRabbit-black?style=for-the-badge&logoColor=white&logo=coderabbit&color=9146FF"/>
  </div>
</div>

# OpenStock

OpenStock is an open-source alternative to expensive market platforms. Track real-time prices, set personalized alerts, and explore detailed company insights — built openly, for everyone, forever free.

Note: OpenStock is community-built and not a brokerage. Market data may be delayed based on provider rules and your configuration. Nothing here is financial advice.

## 📋 Table of Contents

1. ✨ [Introduction](#introduction)
2. 🌍 [Open Dev Society Manifesto](#manifesto)
3. ⚙️ [Tech Stack](#tech-stack)
4. 🔋 [Features](#features)
5. 🤸 [Quick Start](#quick-start)
6. 🐳 [Docker Setup](#docker-setup)
7. 🔐 [Environment Variables](#environment-variables)
8. 🧱 [Project Structure](#project-structure)
9. 📡 [Data & Integrations](#data--integrations)
10. 🧪 [Scripts & Tooling](#scripts--tooling)
11. 🤝 [Contributing](#contributing)
12. 🛡️ [Security](#security)
13. 📜 [License](#license)
14. 🙏 [Acknowledgements](#acknowledgements)

## ✨ Introduction <a name="introduction"></a>

OpenStock is a modern stock market app powered by Next.js (App Router), shadcn/ui and Tailwind CSS, Better Auth for authentication, MongoDB for persistence, Finnhub for market data, and TradingView widgets for charts and market views.

## 🌍 Open Dev Society Manifesto <a name="manifesto"></a>

We live in a world where knowledge is hidden behind paywalls. Where tools are locked in subscriptions. Where information is twisted by bias. Where newcomers are told they’re not “good enough” to build.

We believe there’s a better way.

- Our Belief: Technology should belong to everyone. Knowledge should be open, free, and accessible. Communities should welcome newcomers with trust, not gatekeeping.
- Our Mission: Build free, open-source projects that make a real difference:
    - Tools that professionals and students can use without barriers.
    - Knowledge platforms where learning is free, forever.
    - Communities where every beginner is guided, not judged.
    - Resources that run on trust, not profit.
- Our Promise: We will never lock knowledge. We will never charge for access. We will never trade trust for money. We run on transparency, donations, and the strength of our community.
- Our Call: If you’ve ever felt you didn’t belong, struggled to find free resources, or wanted to build something meaningful — you belong here.

Because the future belongs to those who build it openly.

## ⚙️ Tech Stack <a name="tech-stack"></a>

Core
- Next.js 15 (App Router), React 19
- TypeScript
- Tailwind CSS v4 (via @tailwindcss/postcss)
- shadcn/ui + Radix UI primitives
- Lucide icons

Auth & Data
- Better Auth (email/password) with MongoDB adapter
- MongoDB + Mongoose
- Finnhub API for symbols, profiles, and market news
- TradingView embeddable widgets

Automation & Comms
- Inngest (events, cron, AI inference via Gemini)
- Nodemailer (Gmail transport)
- next-themes, cmdk (command palette), react-hook-form

Language composition
- TypeScript (~93.4%), CSS (~6%), JavaScript (~0.6%)

## 🔋 Features <a name="features"></a>

- Authentication
    - Email/password auth with Better Auth + MongoDB adapter
    - Protected routes enforced via Next.js middleware
- Global search and Command + K palette
    - Fast stock search backed by Finnhub
    - Popular stocks when idle; debounced querying
- Watchlist
    - Per-user watchlist stored in MongoDB (unique symbol per user)
- Stock details
    - TradingView symbol info, candlestick/advanced charts, baseline, technicals
    - Company profile and financials widgets
- Market overview
    - Heatmap, quotes, and top stories (TradingView widgets)
- Personalized onboarding
    - Collects country, investment goals, risk tolerance, preferred industry
- Email & automation
    - AI-personalized welcome email (Gemini via Inngest)
    - Daily news summary emails (cron) personalized using user watchlists
- Polished UI
    - shadcn/ui components, Radix primitives, Tailwind v4 design tokens
    - Dark theme by default
- Keyboard shortcut
    - Cmd/Ctrl + K for quick actions/search

## 🤸 Quick Start <a name="quick-start"></a>

Prerequisites
- Node.js 20+ and pnpm or npm
- MongoDB connection string (MongoDB Atlas or local via Docker Compose)
- Finnhub API key (free tier supported; real-time may require paid)
- Gmail account for email (or update Nodemailer transport)
- Optional: Google Gemini API key (for AI-generated welcome intros)

Clone and install
```bash
git clone https://github.com/Open-Dev-Society/OpenStock.git
cd OpenStock

# choose one:
pnpm install
# or
npm install
```

Configure environment
- Create a `.env` file (see [Environment Variables](#environment-variables)).
- Verify DB connectivity:
```bash
pnpm test:db
# or
npm run test:db
```

Run development
```bash
# Next.js dev (Turbopack)
pnpm dev
# or
npm run dev
```

Run Inngest locally (workflows, cron, AI)
```bash
npx inngest-cli@latest dev
```

Build & start (production)
```bash
pnpm build && pnpm start
# or
npm run build && npm start
```

Open http://localhost:3000 to view the app.

## 🐳 Docker Setup <a name="docker-setup"></a>

You can run OpenStock and MongoDB easily with Docker Compose.

1) Ensure Docker and Docker Compose are installed.

2) docker-compose.yml includes two services:
- openstock (this app)
- mongodb (MongoDB database with a persistent volume)

3) Create your `.env` (see examples below). For the Docker setup, use a local connection string like:
```env
MONGODB_URI=mongodb://root:example@mongodb:27017/openstock?authSource=admin
```

4) Start the stack:
```bash
# from the repository root
docker compose up -d mongodb && docker compose up -d --build
```

5) Access the app:
- App: http://localhost:3000
- MongoDB is available inside the Docker network at host mongodb:27017

Notes
- The app service depends_on the mongodb service.
- Credentials are defined in Compose for the MongoDB root user; authSource=admin is required on the connection string for root.
- Data persists across restarts via the docker volume.

Optional: Example MongoDB service definition used in this project:
```yaml
services:
  mongodb:
    image: mongo:7
    container_name: mongodb
    restart: unless-stopped
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: example
    ports:
      - "27017:27017"
    volumes:
      - mongo-data:/data/db
    healthcheck:
      test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  mongo-data:
```

## 🔐 Environment Variables <a name="environment-variables"></a>

Create `.env` at the project root. Choose either a hosted MongoDB (Atlas) URI or the local Docker URI.

Hosted (MongoDB Atlas):
```env
# Core
NODE_ENV=development

# Database (Atlas)
MONGODB_URI=mongodb+srv://<user>:<pass>@<cluster>/<db>?retryWrites=true&w=majority

# Better Auth
BETTER_AUTH_SECRET=your_better_auth_secret
BETTER_AUTH_URL=http://localhost:3000

# Finnhub
# Note: NEXT_PUBLIC_FINNHUB_API_KEY is required for Vercel deployment
NEXT_PUBLIC_FINNHUB_API_KEY=your_finnhub_key
FINNHUB_BASE_URL=https://finnhub.io/api/v1

# Inngest AI (Gemini)
GEMINI_API_KEY=your_gemini_api_key
# Inngest Signing Key (required for Vercel deployment)
# Get this from your Inngest dashboard: https://app.inngest.com/env/settings/keys
INNGEST_SIGNING_KEY=your_inngest_signing_key

# Email (Nodemailer via Gmail; consider App Passwords if 2FA)
NODEMAILER_EMAIL=youraddress@gmail.com
NODEMAILER_PASSWORD=your_gmail_app_password
```

Local (Docker Compose) MongoDB:
```env
# Core
NODE_ENV=development

# Database (Docker)
MONGODB_URI=mongodb://root:example@mongodb:27017/openstock?authSource=admin

# Better Auth
BETTER_AUTH_SECRET=your_better_auth_secret
BETTER_AUTH_URL=http://localhost:3000

# Finnhub
# Note: NEXT_PUBLIC_FINNHUB_API_KEY is required for Vercel deployment
NEXT_PUBLIC_FINNHUB_API_KEY=your_finnhub_key
FINNHUB_BASE_URL=https://finnhub.io/api/v1

# Inngest AI (Gemini)
GEMINI_API_KEY=your_gemini_api_key
# Inngest Signing Key (required for Vercel deployment)
# Get this from your Inngest dashboard: https://app.inngest.com/env/settings/keys
INNGEST_SIGNING_KEY=your_inngest_signing_key

# Email (Nodemailer via Gmail; consider App Passwords if 2FA)
NODEMAILER_EMAIL=youraddress@gmail.com
NODEMAILER_PASSWORD=your_gmail_app_password
```

Notes
- Keep private keys server-side whenever possible.
- If using `NEXT_PUBLIC_` variables, remember they are exposed to the browser.
- In production, prefer a dedicated SMTP provider over a personal Gmail.
- Do not hardcode secrets in the Dockerfile; use `.env` and Compose.

## 🧱 Project Structure <a name="project-structure"></a>

```
app/
  (auth)/
    layout.tsx
    sign-in/page.tsx
    sign-up/page.tsx
  (root)/
    layout.tsx
    page.tsx
    help/page.tsx
    stocks/[symbol]/page.tsx
  api/inngest/route.ts
  globals.css
  layout.tsx
components/
  ui/…          # shadcn/radix primitives (button, dialog, command, input, etc.)
  forms/…       # InputField, SelectField, CountrySelectField, FooterLink
  Header.tsx, Footer.tsx, SearchCommand.tsx, WatchlistButton.tsx, …
database/
  models/watchlist.model.ts
  mongoose.ts
lib/
  actions/…     # server actions (auth, finnhub, user, watchlist)
  better-auth/…
  inngest/…     # client, functions, prompts
  nodemailer/…  # transporter, email templates
  constants.ts, utils.ts
scripts/
  test-db.mjs
types/
  global.d.ts
next.config.ts          # i.ibb.co image domain allowlist
postcss.config.mjs      # Tailwind v4 postcss setup
components.json         # shadcn config
public/assets/images/   # logos and screenshots
```

## 📡 Data & Integrations <a name="data--integrations"></a>

- Finnhub
    - Stock search, company profiles, and market news.
    - Set `NEXT_PUBLIC_FINNHUB_API_KEY` and `FINNHUB_BASE_URL` (default: https://finnhub.io/api/v1).
    - Free tiers may return delayed quotes; respect rate limits and terms.

- TradingView
    - Embeddable widgets used for charts, heatmap, quotes, and timelines.
    - External images from `i.ibb.co` are allowlisted in `next.config.ts`.

- Better Auth + MongoDB
    - Email/password with MongoDB adapter.
    - Session validation via middleware; most routes are protected, with public exceptions for `sign-in`, `sign-up`, assets and Next internals.

- Inngest
    - Workflows:
        - `app/user.created` → AI-personalized Welcome Email
        - Cron `0 12 * * *` → Daily News Summary per user
    - Local dev: `npx inngest-cli@latest dev`.

- Email (Nodemailer)
    - Gmail transport. Update credentials or switch to your SMTP provider.
    - Templates for welcome and news summary emails.

## 🧪 Scripts & Tooling <a name="scripts--tooling"></a>

Package scripts
- `dev`: Next.js dev server with Turbopack
- `build`: Production build (Turbopack)
- `start`: Run production server
- `lint`: ESLint
- `test:db`: Validate DB connectivity

Developer experience
- TypeScript strict mode
- Tailwind CSS v4 (no separate tailwind.config needed)
- shadcn/ui components with Radix primitives
- cmdk command palette, next-themes, lucide-react icons

## 🤝 Contributing <a name="contributing"></a>

You belong here. Whether you’re a student, a self-taught dev, or a seasoned engineer — contributions are welcome.

- Open an issue to discuss ideas and bugs
- Look for “good first issue” or “help wanted”
- Keep PRs focused; add screenshots for UI changes
- Be kind, guide beginners, no gatekeeping — that’s the ODS way

## 🛡️ Security <a name="security"></a>

If you discover a vulnerability:
- Do not open a public issue
- Email: <a href="mailto:opendevsociety@cc.cc">opendevsociety@cc.cc</a>
- We'll coordinate responsible disclosure and patch swiftly

## 📜 License <a name="license"></a>

OpenStock is and will remain free and open for everyone. This project is licensed under the AGPL-3.0 License - see the LICENSE file for details.

## 🙏 Acknowledgements <a name="acknowledgements"></a>

- Finnhub for accessible market data
- TradingView for embeddable market widgets
- shadcn/ui, Radix UI, Tailwind CSS, Next.js community
- Inngest for dependable background jobs and workflows
- Better Auth for simple and secure authentication
- All contributors who make open tools possible

— Built openly, for everyone, forever free. Open Dev Society.

> © Open Dev Society. This project is licensed under AGPL-3.0; if you modify, redistribute, or deploy it (including as a web service), you must release your source code under the same license and credit the original authors.

## Our Honourable Contributors
- [ravixalgorithm](https://github.com/ravixalgorithm) - Developed the entire application from the ground up, including authentication, UI design, API and AI integration, and deployment.
- [Priyanshuu00007](https://github.com/Priyanshuu00007) - Created the official OpenStock logo and contributed to the project’s visual identity.
- [chinnsenn](https://github.com/chinnsenn) - Set up Docker configuration for the repository, ensuring a smooth development and deployment process.
- [koevoet1221](https://github.com/koevoet1221) - Resolved MongoDB Docker build issues, improving the project’s overall stability and reliability.
- [ettoreciolli1](https://github.com/ettoreciolli1) - updated Readme



## ❤️ Partners & Backers

<a href="https://www.siray.ai/">
  <img src="public/assets/icons/siray.svg" alt="Siray.ai Logo" width="100" />
</a>

**[Siray.ai](https://www.siray.ai/)** — The robust AI infrastructure backing OpenStock. Siray.ai ensures our market insights never sleep.

## Special thanks
Huge thanks to [Adrian Hajdin (JavaScript Mastery)](https://github.com/adrianhajdin) — his excellent Stock Market App tutorial was instrumental in building OpenStock for the open-source community under the Open Dev Society.

GitHub: [adrianhajdin](https://github.com/adrianhajdin)
YouTube tutorial: [Stock Market App Tutorial](https://www.youtube.com/watch?v=gu4pafNCXng)
YouTube channel: [JavaScript Mastery](https://www.youtube.com/@javascriptmastery)



================================================
FILE: app/(auth)/layout.tsx
================================================
import Link from "next/link";
import React from "react";
import Image from "next/image";
import {headers} from "next/headers";
import {redirect} from "next/navigation";
import {auth} from "@/lib/better-auth/auth";

const Layout = async ({ children }: { children : React.ReactNode }) => {

    const session = await auth.api.getSession({headers: await headers()});

    if (session?.user) redirect('/')
    return (
        <main className="auth-layout">
            <section className="auth-left-section scrollbar-hide-default">
                <Link href="/" className="auth-logo flex items-center gap-2">
                    <Image src="/assets/images/logo.png" alt="Openstock" width={200} height={50}/>
                </Link>

                <div className="pb-6 lg:pb-8 flex-1">
                    {children}
                </div>
            </section>
            <section className="auth-right-section">
                <div className="z-10 relative lg:mt-4 lg:mb-16">
                    <blockquote className="auth-blockquote">
                        “For me, OpenStock isn’t just another stock app. It’s about giving people clarity and control in the market, without barriers or subscriptions.”
                    </blockquote>
                    <div className="flex items-center justify-between">
                        <div>
                            <cite className="auth-testimonial-author">- Ravi Pratap Singh (@ravixalgorithm)</cite>
                            <p className="max-md:text-xs text-gray-500">Founder @opendevsociety</p>
                        </div>
                        <div className="flex items-center gap-0.5">
                            {[1,2,3,4,5].map((star) => (
                                <Image src="/assets/icons/star.svg" alt="star" key={star} width={20} height={20} className="w-4 h-4"/>
                            ))}
                        </div>
                    </div>
                </div>
                <div className="flex-1 relative">
                    <Image src="/assets/images/dashboard.png" alt="Dashboard Preview" width={1440} height={1150} className="auth-dashboard-preview absolute top-0" />
                </div>
            </section>

        </main>
    )
}
export default Layout


================================================
FILE: app/(auth)/sign-in/page.tsx
================================================
'use client';

import { useForm } from 'react-hook-form';
import { Button } from '@/components/ui/button';
import InputField from '@/components/forms/InputField';
import FooterLink from '@/components/forms/FooterLink';
import { signInWithEmail, signUpWithEmail } from "@/lib/actions/auth.actions";
import { toast } from "sonner";
import { signInEmail } from "better-auth/api";
import { useRouter } from "next/navigation";
import OpenDevSocietyBranding from "@/components/OpenDevSocietyBranding";
import React from "react";

const SignIn = () => {
    const router = useRouter()
    const {
        register,
        handleSubmit,
        formState: { errors, isSubmitting },
    } = useForm<SignInFormData>({
        defaultValues: {
            email: '',
            password: '',
        },
        mode: 'onBlur',
    });

    const onSubmit = async (data: SignInFormData) => {
        try {
            const result = await signInWithEmail(data);
            if (result.success) {
                router.push('/');
                return;
            }
            toast.error('Sign in failed', {
                description: result.error ?? 'Invalid email or password.',
            });
        } catch (e) {
            console.error(e);
            toast.error('Sign in failed', {
                description: e instanceof Error ? e.message : 'Failed to sign in.'
            })
        }
    }

    return (
        <>
            <h1 className="form-title">Welcome back</h1>

            <form onSubmit={handleSubmit(onSubmit)} className="space-y-5">
                <InputField
                    name="email"
                    label="Email"
                    placeholder="opendevsociety@cc.cc"
                    register={register}
                    error={errors.email}
                    validation={{
                        required: 'Email is required',
                        pattern: {
                            value: /^[\w-.]+@([\w-]+\.)+[\w-]{2,}$/,
                            message: 'Please enter a valid email address'
                        }
                    }}
                />

                <InputField
                    name="password"
                    label="Password"
                    placeholder="Enter your password"
                    type="password"
                    register={register}
                    error={errors.password}
                    validation={{ required: 'Password is required', minLength: 8 }}
                />

                <Button type="submit" disabled={isSubmitting} className="yellow-btn w-full mt-5">
                    {isSubmitting ? 'Signing In' : 'Sign In'}
                </Button>

                <FooterLink text="Don't have an account?" linkText="Create an account" href="/sign-up" />
                <OpenDevSocietyBranding outerClassName="mt-10 flex justify-center" />
                <div className="mt-5 flex justify-center">
                    <a href="https://peerlist.io/ravixalgorithm/project/openstock" target="_blank" rel="noreferrer">
                        <img
                            src="https://peerlist.io/api/v1/projects/embed/PRJH8OED7MBL9MGB9HRMKAKLM66KNN?showUpvote=true&theme=light"
                            alt="OpenStock"
                            style={{ width: 'auto', height: '72px' }}
                        />
                    </a>
                </div>
            </form>
        </>
    );
};
export default SignIn;


================================================
FILE: app/(auth)/sign-up/page.tsx
================================================
'use client';

import { useForm } from "react-hook-form";
import { Button } from "@/components/ui/button";
import InputField from "@/components/forms/InputField";
import SelectField from "@/components/forms/SelectField";
import { INVESTMENT_GOALS, PREFERRED_INDUSTRIES, RISK_TOLERANCE_OPTIONS } from "@/lib/constants";
import { CountrySelectField } from "@/components/forms/CountrySelectField";
import FooterLink from "@/components/forms/FooterLink";
import { signUpWithEmail } from "@/lib/actions/auth.actions";
import { useRouter } from "next/navigation";
import { toast } from "sonner";
import OpenDevSocietyBranding from "@/components/OpenDevSocietyBranding";
import React from "react";

const SignUp = () => {
    const router = useRouter()
    const {
        register,
        handleSubmit,
        control,
        formState: { errors, isSubmitting },
    } = useForm<SignUpFormData>({
        defaultValues: {
            fullName: '',
            email: '',
            password: '',
            country: 'IN',
            investmentGoals: 'Growth',
            riskTolerance: 'Medium',
            preferredIndustry: 'Technology'
        },
        mode: 'onBlur'
    },);

    const onSubmit = async (data: SignUpFormData) => {
        try {
            const result = await signUpWithEmail(data);
            if (result.success) {
                router.push('/');
                return;
            }
            toast.error('Sign up failed', {
                description: result.error ?? 'We could not create your account.',
            });
        } catch (e) {
            console.error(e);
            toast.error('Sign up failed', {
                description: e instanceof Error ? e.message : 'Failed to create an account.'
            })
        }
    }

    return (
        <>
            <h1 className="form-title">Sign Up & Personalize</h1>

            <form onSubmit={handleSubmit(onSubmit)} className="space-y-5">
                <InputField
                    name="fullName"
                    label="Full Name"
                    placeholder="Enter full name"
                    register={register}
                    error={errors.fullName}
                    validation={{ required: 'Full name is required', minLength: 2 }}
                />

                <InputField
                    name="email"
                    label="Email"
                    placeholder="opendevsociety@cc.cc"
                    register={register}
                    error={errors.email}
                    validation={{
                        required: 'Email is required',
                        pattern: {
                            value: /^[\w-.]+@([\w-]+\.)+[\w-]{2,}$/,
                            message: 'Please enter a valid email address'
                        }
                    }}
                />

                <InputField
                    name="password"
                    label="Password"
                    placeholder="Enter a strong password"
                    type="password"
                    register={register}
                    error={errors.password}
                    validation={{ required: 'Password is required', minLength: 8 }}
                />

                <CountrySelectField
                    name="country"
                    label="Country"
                    control={control}
                    error={errors.country}
                    required
                />

                <SelectField
                    name="investmentGoals"
                    label="Investment Goals"
                    placeholder="Select your investment goal"
                    options={INVESTMENT_GOALS}
                    control={control}
                    error={errors.investmentGoals}
                    required
                />

                <SelectField
                    name="riskTolerance"
                    label="Risk Tolerance"
                    placeholder="Select your risk level"
                    options={RISK_TOLERANCE_OPTIONS}
                    control={control}
                    error={errors.riskTolerance}
                    required
                />

                <SelectField
                    name="preferredIndustry"
                    label="Preferred Industry"
                    placeholder="Select your preferred industry"
                    options={PREFERRED_INDUSTRIES}
                    control={control}
                    error={errors.preferredIndustry}
                    required
                />

                <Button type="submit" disabled={isSubmitting} className="yellow-btn w-full mt-5">
                    {isSubmitting ? 'Creating Account' : 'Start Your Investing Journey'}
                </Button>

                <FooterLink text="Already have an account?" linkText="Sign in" href="/sign-in" />

                <OpenDevSocietyBranding outerClassName="mt-10 flex justify-center" />
                <div className="mt-5 flex justify-center">
                    <a href="https://peerlist.io/ravixalgorithm/project/openstock" target="_blank" rel="noreferrer">
                        <img
                            src="https://peerlist.io/api/v1/projects/embed/PRJH8OED7MBL9MGB9HRMKAKLM66KNN?showUpvote=true&theme=light"
                            alt="OpenStock"
                            style={{ width: 'auto', height: '72px' }}
                        />
                    </a>
                </div>
            </form>
        </>
    )
}
export default SignUp;


================================================
FILE: app/(root)/about/page.tsx
================================================

import React from 'react';
import Image from 'next/image';
import Link from 'next/link';
import {
    Users,
    Globe,
    Heart,
    Code,
    Github,
    Twitter,
    Linkedin,
    ArrowRight
} from 'lucide-react';

export const metadata = {
    title: 'About Us | OpenStock',
    description: 'The story behind OpenStock and the Open Dev Society.',
};

export default function AboutPage() {
    return (
        <div className="max-w-5xl mx-auto pb-20 px-4">
            {/* Hero Section */}
            <section className="text-center space-y-8 pt-16 mb-20">
                <div className="flex justify-center mb-6">
                    <div className="p-4 rounded-2xl border border-teal-500/20 backdrop-blur-sm">
                        <img src="/assets/images/logo.png" alt="Open Dev Society" className="h-10 w-auto" />
                    </div>
                </div>

                <h1 className="text-5xl md:text-7xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-white via-gray-200 to-gray-500 tracking-tight">
                    Tools for Everyone.
                </h1>
                <p className="text-xl md:text-2xl text-gray-400 max-w-3xl mx-auto leading-relaxed font-light">
                    We believe financial intelligence shouldn't be locked behind paywalls.
                    OpenStock is built by the community, for the community.
                </p>
            </section>

            {/* Mission Grid */}
            <section className="grid md:grid-cols-3 gap-6 mb-24">
                <FeatureCard
                    icon={<Globe className="text-blue-400" />}
                    title="Open Access"
                    desc="No premium tiers for core features. Real-time data and insights available to all, forever."
                    color="blue"
                />
                <FeatureCard
                    icon={<Code className="text-purple-400" />}
                    title="Open Source"
                    desc="Fully transparent codebase. Audit our algorithms, contribute features, and build with us."
                    color="purple"
                />
                <FeatureCard
                    icon={<Heart className="text-red-400" />}
                    title="Community Driven"
                    desc="Powered by donations and volunteers. We answer to our users, not shareholders."
                    color="red"
                />
            </section>

            {/* Story Section */}
            <section className="grid md:grid-cols-2 gap-12 items-center mb-24 bg-gray-900/30 p-8 md:p-12 rounded-3xl border border-gray-800">
                <div className="space-y-6">
                    <h2 className="text-3xl font-bold text-white">The Open Dev Society</h2>
                    <p className="text-gray-400 leading-relaxed text-lg">
                        OpenStock was born from a simple frustration: why are powerful financial tools so expensive?
                    </p>
                    <p className="text-gray-400 leading-relaxed text-lg">
                        We are a collective of developers, designers, and financial enthusiasts working under the <span className="text-teal-400 font-semibold">Open Dev Society</span> banner. Our mission is to democratize software by building high-quality, open-source alternatives to proprietary platforms.
                    </p>
                    <div className="pt-4">
                        <Link href="https://github.com/Open-Dev-Society" target="_blank" className="inline-flex items-center gap-2 text-teal-400 hover:text-teal-300 font-medium transition-colors group">
                            Visit our GitHub <ArrowRight size={16} className="group-hover:translate-x-1 transition-transform" />
                        </Link>
                    </div>
                </div>
                <div className="relative h-[400px] w-full bg-gradient-to-br from-gray-800 to-black rounded-2xl overflow-hidden border border-gray-700 shadow-2xl group">
                    <Image
                        src="/assets/icons/odslogo.svg"
                        alt="Open Dev Society"
                        fill
                        className="object-contain p-20 opacity-80 group-hover:scale-105 transition-transform duration-700"
                    />
                </div>
            </section>

            {/* Team / Contributors */}
            <section className="text-center mb-20">
                <h2 className="text-3xl font-bold text-white mb-10">Backed by Amazing Partners</h2>
                <div className="flex flex-wrap justify-center items-center gap-8 md:gap-16 opacity-80 grayscale hover:grayscale-0 transition-all duration-500">
                    <div className="h-8 w-px bg-gray-700"></div>
                    <Link href="https://www.siray.ai" target="_blank" className="hover:opacity-100 transition-opacity flex items-center gap-2">
                        <img src="/assets/icons/siray.svg" alt="Siray" className="h-6 w-auto invert brightness-0" />
                        <span className="text-xl font-bold text-teal-500">Siray.ai</span>
                    </Link>
                    <div className="h-8 w-px bg-gray-700"></div>
                </div>
            </section>

        </div>
    );
}

function FeatureCard({ icon, title, desc, color }: any) {
    const borders: any = {
        blue: 'hover:border-blue-500/50',
        purple: 'hover:border-purple-500/50',
        red: 'hover:border-red-500/50',
    };

    return (
        <div className={`bg-gray-900/50 border border-gray-800 p-8 rounded-2xl transition-all duration-300 hover:-translate-y-1 ${borders[color]}`}>
            <div className="mb-6 p-3 bg-gray-800 w-fit rounded-xl">{icon}</div>
            <h3 className="text-xl font-bold text-white mb-3">{title}</h3>
            <p className="text-gray-400 leading-relaxed font-light">{desc}</p>
        </div>
    );
}

function SocialButton({ href, icon, label }: any) {
    return (
        <a
            href={href}
            target="_blank"
            className="flex items-center gap-3 px-6 py-3 bg-gray-800 hover:bg-gray-700 text-white rounded-xl transition-all duration-200 border border-gray-700 hover:border-gray-600 font-medium"
        >
            {icon}
            <span>{label}</span>
        </a>
    );
}


================================================
FILE: app/(root)/api-docs/page.tsx
================================================

import React from 'react';
import Image from 'next/image';
import Link from 'next/link';
import {
  Server,
  Cpu,
  ShieldCheck,
  Clock,
  Database,
  Mail,
  BarChart2,
  Zap,
  ArrowRight,
  CheckCircle2,
  AlertTriangle
} from 'lucide-react';

export const metadata = {
  title: 'API & Architecture | OpenStock',
  description: 'Technical documentation for OpenStock architecture, AI integrations, and background jobs.',
};

export default function ApiDocsPage() {
  return (
    <div className="max-w-5xl mx-auto space-y-16 pb-20">
      {/* Hero Section */}
      <section className="text-center space-y-6 pt-10">
        <div className="flex justify-center items-center gap-4 mb-8">
          <div className="bg-gray-800 p-3 rounded-2xl border border-gray-700 shadow-xl">
            <img src="/assets/images/logo.png" alt="openstock" className="h-10 w-auto invert brightness-0" />
          </div>
          <span className="text-gray-600 text-2xl">+</span>
          <div className="bg-gray-800 p-3 rounded-2xl border border-gray-700 shadow-xl">
            <img src="/assets/icons/siray.svg" alt="Siray" className="h-10 w-auto invert brightness-0" />
          </div>
        </div>

        <h1 className="text-4xl md:text-6xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-white to-gray-400">
          OpenStock Architecture
        </h1>
        <p className="text-xl text-gray-400 max-w-2xl mx-auto leading-relaxed">
          A transparent look at the event-driven, multi-provider system powering your market insights.
        </p>

        <div className="flex flex-wrap justify-center gap-3 pt-2">
          <Badge color="green">v1.0.0 Active</Badge>
          <Badge color="purple">Gemini + Siray AI</Badge>
          <Badge color="blue">Open Source AGPL-3.0</Badge>
        </div>
      </section>

      {/* AI Architecture Section */}
      <section className="grid md:grid-cols-2 gap-8 items-start">
        <div className="space-y-6">
          <div className="flex items-center gap-3">
            <Cpu className="text-teal-400 h-8 w-8" />
            <h2 className="text-3xl font-bold text-gray-100">Intelligent UI</h2>
          </div>
          <p className="text-gray-400 leading-relaxed">
            We prioritize uptime for generative features (Welcome Emails, News Summaries) using a robust
            multi-provider strategy. Our system automatically routes around outages.
          </p>

          <div className="bg-gray-800/50 border border-gray-700 rounded-xl p-6 space-y-4">
            <div className="flex items-start gap-4">
              <div className="bg-teal-500/10 p-2 rounded-lg text-teal-400">
                <Zap size={20} />
              </div>
              <div>
                <h3 className="text-white font-semibold flex items-center gap-2">
                  Primary: Google Gemini
                  <span className="text-[10px] bg-teal-500/10 text-teal-400 px-2 py-0.5 rounded-full border border-teal-500/20">Flash Lite 2.5</span>
                </h3>
                <p className="text-sm text-gray-500 mt-1">
                  Handles high-volume inference for news summarization and personalization.
                </p>
              </div>
            </div>

            <div className="h-px bg-gray-700 w-full" />

            <div className="flex items-start gap-4">
              <div className="bg-blue-500/10 p-2 rounded-lg text-blue-400">
                <ShieldCheck size={20} />
              </div>
              <div>
                <h3 className="text-white font-semibold flex items-center gap-2">
                  Fallback: Siray.ai
                  <span className="text-[10px] bg-blue-500/10 text-blue-400 px-2 py-0.5 rounded-full border border-blue-500/20">Ultra 1.0</span>
                </h3>
                <p className="text-sm text-gray-500 mt-1">
                  Instant failover protection. If Gemini wavers, Siray takes over to ensure zero dropped requests.
                </p>
              </div>
            </div>
          </div>
        </div>

        {/* Diagram / Visual */}
        <div className="bg-[#0A0A0A] border border-gray-800 rounded-xl p-8 flex flex-col justify-center items-center relative overflow-hidden group">
          <div className="absolute inset-0 bg-[radial-gradient(ellipse_at_center,_var(--tw-gradient-stops))] from-teal-900/10 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-700" />

          {/* Visual Flowchart */}
          <div className="relative z-10 flex flex-col items-center gap-6 w-full max-w-sm">
            <div className="bg-gray-800 text-gray-300 px-4 py-2 rounded-lg text-sm border border-gray-700 w-full text-center">
              User Action / Cron Job
            </div>
            <div className="h-6 w-px bg-gray-700" />
            <div className="bg-gray-800 p-4 rounded-xl border border-gray-600 w-full flex flex-col gap-3 relative shadow-2xl">
              <div className="absolute -left-3 top-1/2 -translate-y-1/2 w-1 h-12 bg-teal-500 rounded-full" />
              <span className="text-xs font-mono text-teal-500 mb-1">Inngest Function</span>
              <div className="flex items-center justify-between text-sm text-gray-200 bg-black/40 p-2 rounded border border-gray-700">
                <span>Attempt Gemini</span>
                <CheckCircle2 size={14} className="text-teal-500" />
              </div>
              <div className="flex items-center justify-between text-sm text-gray-200 bg-blue-900/20 p-2 rounded border border-blue-800/50">
                <span className="flex items-center gap-2">
                  Fallback to Siray
                  <ShieldCheck size={12} className="text-blue-400" />
                </span>
                <ArrowRight size={14} className="text-blue-400" />
              </div>
            </div>
            <div className="h-6 w-px bg-gray-700" />
            <div className="bg-green-900/20 text-green-400 px-4 py-2 rounded-lg text-sm border border-green-900/50 w-full text-center font-medium">
              Content Delivered
            </div>
          </div>
        </div>
      </section>

      {/* Background Jobs */}
      <section>
        <div className="flex items-center gap-3 mb-6">
          <Server className="text-purple-400 h-8 w-8" />
          <h2 className="text-3xl font-bold text-gray-100">Serverless Infrastructure</h2>
        </div>

        <div className="grid md:grid-cols-2 lg:grid-cols-4 gap-4">
          <JobCard
            icon={<Mail size={20} />}
            title="Sign Up Email"
            trigger="Event"
            desc="Generates personalized welcome/onboarding email via AI."
            color="purple"
          />
          <JobCard
            icon={<BarChart2 size={20} />}
            title="Weekly News"
            trigger="Cron: Mon 9am"
            desc="Summarizes market news and broadcasts via ConvertKit."
            color="teal"
          />
          <JobCard
            icon={<Clock size={20} />}
            title="Stock Alerts"
            trigger="Cron: 5m"
            desc="Checks user price targets against real-time data."
            color="yellow"
          />
          <JobCard
            icon={<AlertTriangle size={20} />}
            title="Re-engagement"
            trigger="Cron: Daily"
            desc="Identifies dormant users and sends nudges."
            color="red"
          />
        </div>
      </section>

      {/* Integration Stack */}
      <section className="space-y-6">
        <div className="flex items-center gap-3">
          <Database className="text-blue-400 h-8 w-8" />
          <h2 className="text-3xl font-bold text-gray-100">Tech Stack & Data</h2>
        </div>

        <div className="grid gap-4">
          <StackItem
            title="Finnhub"
            desc="Real-time quotes, technical indicators, and market news."
            url="https://finnhub.io"
          />
          <StackItem
            title="ConvertKit (Kit)"
            desc="High-volume newsletter broadcasts and user tagging."
            url="https://kit.com"
          />
          <StackItem
            title="MongoDB Atlas"
            desc="Distributed data on AWS. SRV-bypassed connection for maximum reliability."
            url="https://mongodb.com"
          />
        </div>
      </section>

    </div>
  );
}

// Helper Components

function Badge({ children, color }: { children: React.ReactNode, color: 'green' | 'purple' | 'blue' }) {
  const colors = {
    green: 'bg-green-500/10 text-green-400 border-green-500/20',
    purple: 'bg-purple-500/10 text-purple-400 border-purple-500/20',
    blue: 'bg-blue-500/10 text-blue-400 border-blue-500/20',
  };
  return (
    <span className={`px-3 py-1 rounded-full text-xs font-medium border ${colors[color]}`}>
      {children}
    </span>
  );
}

function JobCard({ icon, title, trigger, desc, color }: any) {
  const colorClasses: any = {
    purple: 'text-purple-400 bg-purple-500/10 border-purple-500/20 hover:border-purple-500/40',
    teal: 'text-teal-400 bg-teal-500/10 border-teal-500/20 hover:border-teal-500/40',
    yellow: 'text-yellow-400 bg-yellow-500/10 border-yellow-500/20 hover:border-yellow-500/40',
    red: 'text-red-400 bg-red-500/10 border-red-500/20 hover:border-red-500/40',
  };

  return (
    <div className={`p-5 rounded-xl border transition-all duration-300 ${colorClasses[color]}`}>
      <div className="mb-4">{icon}</div>
      <h3 className="font-bold text-gray-100 text-lg mb-1">{title}</h3>
      <div className="text-xs font-mono opacity-70 mb-3 uppercase tracking-wider">{trigger}</div>
      <p className="text-sm opacity-80 leading-relaxed">{desc}</p>
    </div>
  );
}

function StackItem({ title, desc, url }: any) {
  return (
    <Link href={url} target="_blank" className="block group">
      <div className="bg-gray-800/40 hover:bg-gray-800 p-6 rounded-xl border border-gray-700 hover:border-gray-600 transition-all flex items-center justify-between">
        <div>
          <h3 className="text-xl font-bold text-gray-200 group-hover:text-teal-400 transition-colors">{title}</h3>
          <p className="text-gray-500 mt-1">{desc}</p>
        </div>
        <ArrowRight className="text-gray-600 group-hover:text-teal-400 transition-colors" />
      </div>
    </Link>
  );
}


================================================
FILE: app/(root)/help/page.tsx
================================================
import { Metadata } from 'next';
import {
  HelpCircle,
  MessageCircle,
  BookOpen,
  Lightbulb,
  Mail,
  Github,
  ChevronDown
} from 'lucide-react';

export const metadata: Metadata = {
  title: 'Help Center | OpenStock',
  description: 'Community-driven support for OpenStock. No paywalls, just help.',
};

export default function HelpPage() {
  const faqs = [
    {
      question: "Is OpenStock really free forever?",
      answer: "Yes! We run on donations and community contribution. Core features (tracking, alerts, analysis) will remain free. We believe financial tools shouldn't be luxury items."
    },
    {
      question: "How do I add stocks to my watchlist?",
      answer: "Use the search bar at the top or in the header to find a company. On the stock's detail page, click the 'Heart' or 'Star' icon to instantly add it to your dashboard."
    },
    {
      question: "Where does the market data come from?",
      answer: "We partner with Finnhub and other providers to offer real-time and delayed data. While robust, please use it for analysis rather than high-frequency trading."
    },
    {
      question: "Can I contribute code or designs?",
      answer: "Absolutely! Check our GitHub repository. We label issues as 'good first issue' for beginners. We welcome designers, developers, and writers alike."
    },
    {
      question: "My alerts aren't triggering.",
      answer: "Alerts run every 5 minutes via our background jobs. Ensure you've confirmed your email address, as we send notifications primarily via email."
    }
  ];

  return (
    <div className="max-w-4xl mx-auto px-4 pb-20">

      {/* Header */}
      <div className="text-center pt-16 pb-12 space-y-4">
        <div className="inline-flex p-3 bg-blue-500/10 rounded-2xl border border-blue-500/20 mb-4">
          <HelpCircle className="text-blue-400 h-8 w-8" />
        </div>
        <h1 className="text-4xl md:text-5xl font-bold text-white">How can we help?</h1>
        <p className="text-xl text-gray-400">Community-powered support for everyone.</p>
      </div>

      {/* Quick Action Grid */}
      <div className="grid md:grid-cols-3 gap-4 mb-16">
        <HelpCard
          icon={<BookOpen className="text-teal-400" />}
          title="Read Docs"
          desc="Deep dive into features and API integration."
          link="/api-docs"
          linkText="View Documentation"
        />
        <HelpCard
          icon={<MessageCircle className="text-purple-400" />}
          title="Community Chat"
          desc="Get real-time answers from other users."
          link="https://discord.gg/JkJ8kfxgxB"
          linkText="Join Discord"
        />
        <HelpCard
          icon={<Github className="text-white" />}
          title="Report Bugs"
          desc="Found an issue? Let our developers know."
          link="https://github.com/Open-Dev-Society/OpenStock/issues"
          linkText="Open Issue"
        />
      </div>

      {/* FAQs */}
      <div className="space-y-8">
        <h2 className="text-2xl font-bold text-white border-b border-gray-800 pb-4">Frequently Asked Questions</h2>
        <div className="grid gap-4">
          {faqs.map((faq, idx) => (
            <div key={idx} className="bg-gray-900/50 border border-gray-800 rounded-xl p-6 hover:bg-gray-800/50 transition-colors">
              <h3 className="font-semibold text-lg text-gray-200 mb-2 flex items-start gap-3">
                <Lightbulb size={20} className="text-yellow-500/50 mt-1 shrink-0" />
                {faq.question}
              </h3>
              <p className="text-gray-400 leading-relaxed ml-8 pl-1 border-l-2 border-gray-800">
                {faq.answer}
              </p>
            </div>
          ))}
        </div>
      </div>

      {/* Direct Contact */}
      <div className="mt-20 bg-gradient-to-br from-gray-900 to-black border border-gray-800 rounded-2xl p-8 text-center">
        <h3 className="text-xl font-bold text-white mb-2">Still stuck?</h3>
        <p className="text-gray-400 mb-6">Our team (and community) answers emails, usually entirely for free.</p>
        <a
          href="mailto:opendevsociety@gmail.com"
          className="inline-flex items-center gap-2 bg-white text-black px-6 py-3 rounded-lg font-medium hover:bg-gray-200 transition-colors"
        >
          <Mail size={18} />
          Contact Support
        </a>
      </div>

    </div>
  );
}

function HelpCard({ icon, title, desc, link, linkText }: any) {
  return (
    <div className="bg-gray-900 border border-gray-800 p-6 rounded-xl flex flex-col items-start hover:border-gray-700 transition-colors">
      <div className="mb-4 bg-gray-800 p-2 rounded-lg">{icon}</div>
      <h3 className="font-bold text-white text-lg mb-2">{title}</h3>
      <p className="text-sm text-gray-400 mb-6 flex-grow">{desc}</p>
      <a href={link} className="text-teal-400 text-sm font-medium hover:underline flex items-center gap-1">
        {linkText} <ChevronDown size={14} className="-rotate-90" />
      </a>
    </div>
  );
}


================================================
FILE: app/(root)/layout.tsx
================================================
import Header from "@/components/Header";
import { auth } from "@/lib/better-auth/auth";
import { headers } from "next/headers";
import { redirect } from "next/navigation";
import Footer from "@/components/Footer";
import DonatePopup from "@/components/DonatePopup";
import SirayBanner from "@/components/SirayBanner";

const Layout = async ({ children }: { children: React.ReactNode }) => {
    const session = await auth.api.getSession({ headers: await headers() });

    if (!session?.user) redirect('/sign-in');

    const user = {
        id: session.user.id,
        name: session.user.name,
        email: session.user.email,
    }

    return (
        <main className="min-h-screen text-gray-400">
            <SirayBanner />
            <Header user={user} />

            <div className="container py-10">
                {children}
            </div>

            <Footer />
            <DonatePopup />
        </main>
    )
}
export default Layout

================================================
FILE: app/(root)/page.tsx
================================================
import TradingViewWidget from "@/components/TradingViewWidget";
import {
    HEATMAP_WIDGET_CONFIG,
    MARKET_DATA_WIDGET_CONFIG,
    MARKET_OVERVIEW_WIDGET_CONFIG,
    TOP_STORIES_WIDGET_CONFIG
} from "@/lib/constants";

const Home = () => {
    const scriptUrl = `https://s3.tradingview.com/external-embedding/embed-widget-`;

    return (
        <div className="flex min-h-screen home-wrapper">
            <section className="grid w-full gap-8 home-section">
                <div className="md:col-span-1 xl:col-span-1">
                    <TradingViewWidget
                        title="Market Overview"
                        scriptUrl={`${scriptUrl}market-overview.js`}
                        config={MARKET_OVERVIEW_WIDGET_CONFIG}
                        className="custom-chart"
                        height={600}
                    />
                </div>
                <div className="md-col-span xl:col-span-2">
                    <TradingViewWidget
                        title="Stock Heatmap"
                        scriptUrl={`${scriptUrl}stock-heatmap.js`}
                        config={HEATMAP_WIDGET_CONFIG}
                        height={600}
                    />
                </div>
            </section>
            <section className="grid w-full gap-8 home-section">
                <div className="h-full md:col-span-1 xl:col-span-2">
                    <TradingViewWidget
                        scriptUrl={`${scriptUrl}market-quotes.js`}
                        config={MARKET_DATA_WIDGET_CONFIG}
                        height={600}
                    />
                </div>
                <div className="h-full md:col-span-1 xl:col-span-1">
                    <TradingViewWidget
                        scriptUrl={`${scriptUrl}timeline.js`}
                        config={TOP_STORIES_WIDGET_CONFIG}
                        height={600}
                    />
                </div>

            </section>
            <div className="w-full flex flex-col items-center justify-center mt-8 gap-4">
                <h2 className="text-xl font-semibold text-gray-200">Upvote us on Peerlist 🚀</h2>
                <a href="https://peerlist.io/ravixalgorithm/project/openstock" target="_blank" rel="noreferrer">
                    <img
                        src="https://peerlist.io/api/v1/projects/embed/PRJH8OED7MBL9MGB9HRMKAKLM66KNN?showUpvote=true&theme=light"
                        alt="OpenStock"
                        style={{ width: "auto", height: "72px" }}
                    />
                </a>
            </div>
        </div>
    )
}

export default Home;

================================================
FILE: app/(root)/stocks/[symbol]/page.tsx
================================================
import TradingViewWidget from "@/components/TradingViewWidget";
import WatchlistButton from "@/components/WatchlistButton";
import {
    SYMBOL_INFO_WIDGET_CONFIG,
    CANDLE_CHART_WIDGET_CONFIG,
    BASELINE_WIDGET_CONFIG,
    TECHNICAL_ANALYSIS_WIDGET_CONFIG,
    COMPANY_PROFILE_WIDGET_CONFIG,
    COMPANY_FINANCIALS_WIDGET_CONFIG,
} from "@/lib/constants";

import { auth } from '@/lib/better-auth/auth';
import { headers } from 'next/headers';
import { isStockInWatchlist } from '@/lib/actions/watchlist.actions';
import { formatSymbolForTradingView } from '@/lib/utils';

export default async function StockDetails({ params }: StockDetailsPageProps) {
    const { symbol } = await params;
    const tvSymbol = formatSymbolForTradingView(symbol);
    const scriptUrl = `https://s3.tradingview.com/external-embedding/embed-widget-`;

    const session = await auth.api.getSession({
        headers: await headers()
    });
    const userId = session?.user?.id;
    const isInWatchlist = userId ? await isStockInWatchlist(userId, symbol) : false;

    return (
        <div className="flex min-h-screen p-4 md:p-6 lg:p-8">
            <section className="grid grid-cols-1 md:grid-cols-2 gap-8 w-full">
                {/* Left column */}
                <div className="flex flex-col gap-6">
                    <TradingViewWidget
                        scriptUrl={`${scriptUrl}symbol-info.js`}
                        config={SYMBOL_INFO_WIDGET_CONFIG(tvSymbol)}
                        height={170}
                    />

                    <TradingViewWidget
                        scriptUrl={`${scriptUrl}advanced-chart.js`}
                        config={CANDLE_CHART_WIDGET_CONFIG(tvSymbol)}
                        className="custom-chart"
                        height={600}
                        allowExpand={true}
                    />

                    <TradingViewWidget
                        scriptUrl={`${scriptUrl}advanced-chart.js`}
                        config={BASELINE_WIDGET_CONFIG(tvSymbol)}
                        className="custom-chart"
                        height={600}
                        allowExpand={true}
                    />
                </div>

                {/* Right column */}
                <div className="flex flex-col gap-6">
                    <div className="flex items-center justify-between">
                        <WatchlistButton
                            symbol={symbol.toUpperCase()}
                            company={symbol.toUpperCase()}
                            isInWatchlist={isInWatchlist}
                            userId={userId}
                        />
                    </div>

                    <TradingViewWidget
                        scriptUrl={`${scriptUrl}technical-analysis.js`}
                        config={TECHNICAL_ANALYSIS_WIDGET_CONFIG(tvSymbol)}
                        height={400}
                    />

                    <TradingViewWidget
                        scriptUrl={`${scriptUrl}company-profile.js`}
                        config={COMPANY_PROFILE_WIDGET_CONFIG(tvSymbol)}
                        height={440}
                    />

                    <TradingViewWidget
                        scriptUrl={`${scriptUrl}financials.js`}
                        config={COMPANY_FINANCIALS_WIDGET_CONFIG(tvSymbol)}
                        height={800}
                    />
                </div>
            </section>
        </div>
    );
}

================================================
FILE: app/(root)/terms/page.tsx
================================================
import { Metadata } from 'next';
import { Shield, FileText, Check, AlertTriangle, Scale } from 'lucide-react';

export const metadata: Metadata = {
  title: 'Terms of Service | OpenStock',
  description: 'Fair, transparent, and open terms for our community.',
};

export default function TermsPage() {
  return (
    <div className="max-w-4xl mx-auto px-4 pb-20">

      {/* Hero */}
      <div className="text-center pt-16 pb-12 space-y-4">
        <div className="inline-flex p-3 bg-teal-500/10 rounded-2xl border border-teal-500/20 mb-4">
          <Scale className="text-teal-400 h-8 w-8" />
        </div>
        <h1 className="text-4xl md:text-5xl font-bold text-white">Terms of Service</h1>
        <p className="text-xl text-gray-400 max-w-2xl mx-auto">
          Built on trust, transparency, and community values. No hidden gotchas, just clear rules.
        </p>
        <p className="text-sm text-gray-500">Last updated: October 2025</p>
      </div>

      <div className="space-y-12">
        {/* Core Philosophy */}
        <section className="bg-gray-900 border border-gray-800 rounded-2xl p-8">
          <h2 className="text-2xl font-bold text-white mb-6 flex items-center gap-2">
            <Shield className="text-teal-500" />
            Our Promise
          </h2>
          <div className="grid md:grid-cols-2 gap-6">
            <PromiseItem text="Core features will remain free forever." />
            <PromiseItem text="We will never sell your personal data." />
            <PromiseItem text="Terms changes will be discussed openly." />
            <PromiseItem text="You own your watchlists and analysis." />
          </div>
        </section>

        {/* Disclaimer */}
        <section className="bg-yellow-900/10 border border-yellow-500/20 rounded-2xl p-8">
          <div className="flex items-start gap-4">
            <AlertTriangle className="text-yellow-500 shrink-0 mt-1" size={24} />
            <div>
              <h3 className="text-xl font-bold text-yellow-100 mb-2">Investment Disclaimer</h3>
              <p className="text-yellow-200/80 leading-relaxed">
                **OpenStock is an educational and analysis tool, not a financial advisor.**
                Data is provided "as is" for informational purposes. Never invest money you cannot afford to lose.
                Always conduct your own research or consult a certified professional before making financial decisions.
              </p>
            </div>
          </div>
        </section>

        {/* User Responsibilities */}
        <section>
          <h2 className="text-2xl font-bold text-white mb-6">Community Rules</h2>
          <div className="grid md:grid-cols-2 gap-6">
            <div className="bg-gray-900 border border-gray-800 p-6 rounded-xl">
              <h3 className="text-lg font-semibold text-blue-400 mb-4">✅ Do's</h3>
              <ul className="space-y-3 text-gray-400">
                <li className="flex gap-2"><Check size={16} className="text-blue-500 mt-1" /> Share knowledge freely</li>
                <li className="flex gap-2"><Check size={16} className="text-blue-500 mt-1" /> Use API for personal projects</li>
                <li className="flex gap-2"><Check size={16} className="text-blue-500 mt-1" /> Respect other members</li>
              </ul>
            </div>
            <div className="bg-gray-900 border border-gray-800 p-6 rounded-xl">
              <h3 className="text-lg font-semibold text-red-400 mb-4">❌ Don'ts</h3>
              <ul className="space-y-3 text-gray-400">
                <li className="flex gap-2"><span className="text-red-500 font-bold">×</span> Scrape data excessively</li>
                <li className="flex gap-2"><span className="text-red-500 font-bold">×</span> Share API keys</li>
                <li className="flex gap-2"><span className="text-red-500 font-bold">×</span> Use for high-frequency trading</li>
              </ul>
            </div>
          </div>
        </section>

        {/* Footer Note */}
        <div className="text-center pt-8 border-t border-gray-800">
          <p className="text-gray-500">
            Questions about these terms? Email us at <a href="mailto:opendevsociety@gmail.com" className="text-teal-400 hover:underline">opendevsociety@gmail.com</a>
          </p>
        </div>
      </div>
    </div>
  );
}

function PromiseItem({ text }: { text: string }) {
  return (
    <div className="flex items-center gap-3 bg-gray-800/50 p-4 rounded-lg">
      <div className="bg-teal-500/10 p-1 rounded-full">
        <Check size={14} className="text-teal-400" />
      </div>
      <span className="text-gray-300 font-medium">{text}</span>
    </div>
  );
}


================================================
FILE: app/(root)/watchlist/page.tsx
================================================
import React, { Suspense } from 'react';
import { auth } from '@/lib/better-auth/auth';
import { headers } from 'next/headers';
import { redirect } from 'next/navigation';
import { getUserWatchlist } from '@/lib/actions/watchlist.actions';
import { getUserAlerts } from '@/lib/actions/alert.actions';
import { getNews } from '@/lib/actions/finnhub.actions';
import WatchlistManager from '@/components/watchlist/WatchlistManager';
import AlertsPanel from '@/components/watchlist/AlertsPanel';
import NewsGrid from '@/components/watchlist/NewsGrid';
import SearchCommand from '@/components/SearchCommand';
import { Loader2 } from 'lucide-react';

export default async function WatchlistPage() {
    const session = await auth.api.getSession({
        headers: await headers()
    });

    if (!session) {
        redirect('/sign-in');
    }

    const userId = session.user.id;

    // Parallel data fetching
    const [watchlistItems, alerts, news] = await Promise.all([
        getUserWatchlist(userId),
        getUserAlerts(userId),
        getNews() // Initial news fetch
    ]);

    const watchlistSymbols = watchlistItems.map((item: any) => item.symbol);

    // Fallback news if watchlist has items
    const relevantNews = watchlistSymbols.length > 0 ? await getNews(watchlistSymbols) : news;

    return (
        <div className="min-h-screen bg-black text-gray-100 p-6 md:p-8">
            {/* Header */}
            <div className="flex flex-col md:flex-row md:items-center justify-between mb-8 gap-4">
                <div>
                    <h1 className="text-3xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-white to-gray-500">
                        Watchlist
                    </h1>
                    <p className="text-gray-500 mt-1">Track your favorite stocks and manage alerts.</p>
                </div>
                <div className="flex items-center space-x-4">
                    <SearchCommand renderAs="button" label="Add Stock" initialStocks={[]} />
                </div>
            </div>

            <div className="grid grid-cols-1 lg:grid-cols-4 gap-8">
                {/* Main Content - Watchlist Table */}
                <div className="lg:col-span-3 space-y-8">
                    <div className="space-y-6">
                        <WatchlistManager initialItems={watchlistItems} userId={userId} />
                    </div>

                    {/* News Section */}
                    <Suspense fallback={<div className="flex justify-center p-12"><Loader2 className="animate-spin text-gray-500" /></div>}>
                        <NewsGrid news={relevantNews || []} />
                    </Suspense>
                </div>

                {/* Sidebar - Alerts */}
                <div className="lg:col-span-1">
                    <AlertsPanel alerts={alerts} />
                </div>
            </div>
        </div>
    );
}


================================================
FILE: app/api/inngest/route.ts
================================================
import { serve } from "inngest/next";
import { inngest } from "@/lib/inngest/client";
import { sendWeeklyNewsSummary, sendSignUpEmail, checkStockAlerts, checkInactiveUsers } from "@/lib/inngest/functions";

export const { GET, POST, PUT } = serve({
    client: inngest,
    functions: [sendSignUpEmail, sendWeeklyNewsSummary, checkStockAlerts, checkInactiveUsers],
})

================================================
FILE: app/globals.css
================================================
@import "tailwindcss";
@import "tw-animate-css";

@custom-variant dark (&:is(.dark *));

@theme inline {
    --color-background: var(--background);
    --color-foreground: var(--foreground);
    --font-sans: var(--font-geist-sans);
    --font-mono: var(--font-geist-mono);
    --color-sidebar-ring: var(--sidebar-ring);
    --color-sidebar-border: var(--sidebar-border);
    --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
    --color-sidebar-accent: var(--sidebar-accent);
    --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
    --color-sidebar-primary: var(--sidebar-primary);
    --color-sidebar-foreground: var(--sidebar-foreground);
    --color-sidebar: var(--sidebar);
    --color-chart-5: var(--chart-5);
    --color-chart-4: var(--chart-4);
    --color-chart-3: var(--chart-3);
    --color-chart-2: var(--chart-2);
    --color-chart-1: var(--chart-1);
    --color-ring: var(--ring);
    --color-input: var(--input);
    --color-border: var(--border);
    --color-destructive: var(--destructive);
    --color-accent-foreground: var(--accent-foreground);
    --color-accent: var(--accent);
    --color-muted-foreground: var(--muted-foreground);
    --color-muted: var(--muted);
    --color-secondary-foreground: var(--secondary-foreground);
    --color-secondary: var(--secondary);
    --color-primary-foreground: var(--primary-foreground);
    --color-primary: var(--primary);
    --color-popover-foreground: var(--popover-foreground);
    --color-popover: var(--popover);
    --color-card-foreground: var(--card-foreground);
    --color-card: var(--card);
    --radius-sm: calc(var(--radius) - 4px);
    --radius-md: calc(var(--radius) - 2px);
    --radius-lg: var(--radius);
    --radius-xl: calc(var(--radius) + 4px);
}

:root {
    --radius: 0.625rem;
    --background: oklch(1 0 0);
    --foreground: oklch(0.129 0.042 264.695);
    --card: oklch(1 0 0);
    --card-foreground: oklch(0.129 0.042 264.695);
    --popover: oklch(1 0 0);
    --popover-foreground: oklch(0.129 0.042 264.695);
    --primary: oklch(0.208 0.042 265.755);
    --primary-foreground: oklch(0.984 0.003 247.858);
    --secondary: oklch(0.968 0.007 247.896);
    --secondary-foreground: oklch(0.208 0.042 265.755);
    --muted: oklch(0.968 0.007 247.896);
    --muted-foreground: oklch(0.554 0.046 257.417);
    --accent: oklch(0.968 0.007 247.896);
    --accent-foreground: oklch(0.208 0.042 265.755);
    --destructive: oklch(0.577 0.245 27.325);
    --border: oklch(0.929 0.013 255.508);
    --input: oklch(0.929 0.013 255.508);
    --ring: oklch(0.704 0.04 256.788);
    --chart-1: oklch(0.646 0.222 41.116);
    --chart-2: oklch(0.6 0.118 184.704);
    --chart-3: oklch(0.398 0.07 227.392);
    --chart-4: oklch(0.828 0.189 84.429);
    --chart-5: oklch(0.769 0.188 70.08);
    --sidebar: oklch(0.984 0.003 247.858);
    --sidebar-foreground: oklch(0.129 0.042 264.695);
    --sidebar-primary: oklch(0.208 0.042 265.755);
    --sidebar-primary-foreground: oklch(0.984 0.003 247.858);
    --sidebar-accent: oklch(0.968 0.007 247.896);
    --sidebar-accent-foreground: oklch(0.208 0.042 265.755);
    --sidebar-border: oklch(0.929 0.013 255.508);
    --sidebar-ring: oklch(0.704 0.04 256.788);
}

.dark {
    --background: oklch(0.129 0.042 264.695);
    --foreground: oklch(0.984 0.003 247.858);
    --card: oklch(0.208 0.042 265.755);
    --card-foreground: oklch(0.984 0.003 247.858);
    --popover: oklch(0.208 0.042 265.755);
    --popover-foreground: oklch(0.984 0.003 247.858);
    --primary: oklch(0.929 0.013 255.508);
    --primary-foreground: oklch(0.208 0.042 265.755);
    --secondary: oklch(0.279 0.041 260.031);
    --secondary-foreground: oklch(0.984 0.003 247.858);
    --muted: oklch(0.279 0.041 260.031);
    --muted-foreground: oklch(0.704 0.04 256.788);
    --accent: oklch(0.279 0.041 260.031);
    --accent-foreground: oklch(0.984 0.003 247.858);
    --destructive: oklch(0.704 0.191 22.216);
    --border: oklch(1 0 0 / 10%);
    --input: oklch(1 0 0 / 15%);
    --ring: oklch(0.551 0.027 264.364);
    --chart-1: oklch(0.488 0.243 264.376);
    --chart-2: oklch(0.696 0.17 162.48);
    --chart-3: oklch(0.769 0.188 70.08);
    --chart-4: oklch(0.627 0.265 303.9);
    --chart-5: oklch(0.645 0.246 16.439);
    --sidebar: oklch(0.208 0.042 265.755);
    --sidebar-foreground: oklch(0.984 0.003 247.858);
    --sidebar-primary: oklch(0.488 0.243 264.376);
    --sidebar-primary-foreground: oklch(0.984 0.003 247.858);
    --sidebar-accent: oklch(0.279 0.041 260.031);
    --sidebar-accent-foreground: oklch(0.984 0.003 247.858);
    --sidebar-border: oklch(1 0 0 / 10%);
    --sidebar-ring: oklch(0.551 0.027 264.364);
}

/* === CUSTOM COLOR THEME === */
@theme {
    /* Extended Gray Scale */
    --color-gray-900: #050505;
    --color-gray-800: #141414;
    --color-gray-700: #212328;
    --color-gray-600: #30333A;
    --color-gray-500: #9095A1;
    --color-gray-400: #CCDADC;

    /* Vibrant Colors */
    --color-blue-600: #5862FF;
    --color-yellow-400: #FDD458;
    --color-yellow-500: #E8BA40;
    --color-teal-400: #0FEDBE;
    --color-red-500: #FF495B;
    --color-orange-500: #FF8243;
    --color-purple-500: #D13BFF;
}

@layer base {
    * {
        @apply border-border outline-ring/50;
    }
    body {
        @apply bg-gray-900 text-foreground;
    }
}

@layer utilities {
    .container {
        @apply mx-auto max-w-screen-2xl px-4 md:px-6 lg:px-8;
    }
    .yellow-btn {
        @apply h-12 cursor-pointer bg-gradient-to-b from-teal-400 to-teal-500 hover:from-teal-500 hover:to-teal-400 text-gray-800 font-medium text-base rounded-lg shadow-lg disabled:opacity-50;
    }
    .home-wrapper {
        @apply text-gray-400 flex-col gap-4 md:gap-10 items-center sm:items-start;
    }
    .home-section {
        @apply w-full gap-8 grid-cols-1 md:grid-cols-2 xl:grid-cols-3;
    }
    .header {
        @apply z-50 w-full h-[70px] bg-gray-800;
    }
    .header-wrapper {
        @apply flex justify-between items-center px-6 py-4 text-gray-500;
    }
    .auth-layout {
        @apply flex flex-col justify-between lg:flex-row h-screen bg-gray-900 relative overflow-hidden;
    }
    .auth-logo {
        @apply pt-6 lg:pt-8 mb-8 lg:mb-12;
    }
    .auth-left-section {
        @apply w-full lg:w-[45%] lg:h-screen px-6 lg:px-16 flex flex-col overflow-y-auto;
    }
    .auth-right-section {
        @apply w-full max-lg:border-t max-lg:border-gray-600 lg:w-[55%] lg:h-screen bg-gray-800 px-6 py-4 md:p-6 lg:py-12 lg:px-18 flex flex-col justify-start;
    }
    .auth-blockquote {
        @apply text-sm md:text-xl lg:text-2xl font-medium text-gray-400 mb-1 md:mb-6 lg:mb-8;
    }
    .auth-testimonial-author {
        @apply text-xs md:text-lg font-bold text-gray-400 not-italic;
    }
    .auth-dashboard-preview {
        @apply border-6 border-gray-800 left-0 hidden w-[1024px] h-auto max-w-none lg:block rounded-xl shadow-2xl;
    }
    .form-title {
        @apply text-4xl font-bold text-gray-400 mb-10;
    }
    .form-label {
        @apply text-sm font-medium text-gray-400;
    }
    .form-input {
        @apply h-12 px-3 py-3 text-white text-base placeholder:text-gray-600 border-gray-600  rounded-lg focus:!border-teal-500 focus:ring-0 ;
    }
    .select-trigger {
        @apply w-full !h-12 px-3 py-3 text-base border-gray-600 bg-gray-800 text-white rounded-lg focus:!border-teal-500 focus:ring-0;
    }
    .country-select-trigger {
        @apply h-12 px-3 py-3 text-base w-full justify-between font-normal border-gray-600 bg-gray-800 text-gray-400 rounded-lg focus:!border-teal-500 focus:ring-0;
    }
    .country-select-input {
        @apply !bg-gray-800 text-gray-400 border-0 border-b border-gray-600 rounded-none focus:ring-0 placeholder:text-gray-500;
    }
    .country-select-empty {
        @apply text-gray-500 py-6 text-center !bg-gray-800;
    }
    .country-select-item {
        @apply text-white cursor-pointer px-3 py-2 rounded-sm bg-gray-800 hover:!bg-gray-600;
    }
    .footer-link {
        @apply text-gray-400 font-medium hover:text-teal-400 hover:underline transition-colors;
    }
    .search-text {
        @apply cursor-pointer hover:text-teal-500;
    }
    .search-btn {
        @apply cursor-pointer px-4 py-2 w-fit flex items-center gap-2 text-sm md:text-base bg-teal-500 hover:bg-teal-500 text-black font-medium rounded;
    }
    .search-dialog {
        @apply !bg-gray-800 lg:min-w-[800px] border-gray-600 fixed top-10 left-1/2 -translate-x-1/2 translate-y-10;
    }
    .search-field {
        @apply !bg-gray-800 border-b border-gray-600 relative;
    }
    .search-list {
        @apply !bg-gray-800 max-h-[400px];
    }
    .search-list-indicator {
        @apply px-5 py-2
    }
    .search-list-empty {
        @apply py-6 !bg-transparent text-center text-gray-500;
    }
    .search-input {
        @apply !bg-gray-800 border-0 text-gray-400 placeholder:text-gray-500 focus:ring-0 text-base h-14 pr-10;
    }
    .search-loader {
        @apply absolute right-12 top-1/2 -translate-y-1/2 h-4 w-4 text-gray-500 animate-spin;
    }
    .search-count {
        @apply py-2 px-4 text-sm font-medium text-gray-400 bg-gray-700 border-b border-gray-700;
    }
    .search-item {
        @apply rounded-none my-3 px-1 w-full data-[selected=true]:bg-gray-600;
    }
    .search-item-link {
        @apply px-2 w-full cursor-pointer border-b border-gray-600 last:border-b-0 transition-colors flex items-center gap-3;
    }
    .search-item-name {
        @apply font-medium text-base text-gray-400;
    }
    .nav-list {
        @apply flex flex-col sm:flex-row p-2 gap-3 sm:gap-10 font-medium;
    }
    .stock-details-container {
        @apply w-full grid-cols-1 gap-6 xl:grid-cols-3 space-y-6 sm:space-y-8;
    }
    .watchlist-btn {
        @apply bg-teal-500 text-base hover:bg-teal-500 text-gray-900 w-full rounded h-11 font-semibold cursor-pointer;
    }
    .watchlist-remove {
        @apply bg-red-500! hover:bg-red-500! text-gray-900!
    }
    .watchlist-empty-container {
        @apply container gap-8 flex-col items-center md:mt-10 p-6 text-center;
    }
    .watchlist-empty {
        @apply flex flex-col items-center justify-center text-center;
    }
    .watchlist-star {
        @apply h-16 w-16 text-gray-500 mb-4;
    }
    .empty-title {
        @apply text-xl font-semibold text-gray-400 mb-2;
    }
    .empty-description {
        @apply text-gray-500 mb-6 max-w-md;
    }
    .watchlist-container {
        @apply flex flex-col-reverse lg:grid lg:grid-cols-3 gap-8;
    }
    .watchlist {
        @apply lg:col-span-2 space-y-8;
    }
    .watchlist-alerts {
        @apply items-start gap-6 h-full flex-col w-full lg:col-span-1;
    }
    .watchlist-icon-btn {
        @apply w-fit cursor-pointer hover:bg-transparent! text-gray-400 hover:text-teal-500;
    }
    .watchlist-icon-added {
        @apply !text-teal-500 hover:!text-teal-600;
    }
    .watchlist-icon {
        @apply w-8 h-8 rounded-full flex items-center justify-center bg-gray-700/50;
    }
    .trash-icon {
        @apply h-4 w-4 text-gray-400 hover:text-red-400;
    }
    .star-icon {
        @apply h-4 w-4;
    }
    .watchlist-title {
        @apply text-xl md:text-2xl font-bold text-gray-100;
    }
    .watchlist-table {
        @apply !relative overflow-hidden !w-full bg-gray-800 border !border-gray-600 !rounded-lg;
    }
    .table-header-row {
        @apply text-gray-400 font-medium bg-gray-700 border-b border-gray-600 hover:bg-gray-700;
    }
    .table-header:first-child {
        @apply pl-4;
    }
    .table-row {
        @apply border-b cursor-pointer text-gray-100 border-gray-600 hover:bg-gray-700/50 transition-colors;
    }
    .table-cell {
        @apply font-medium text-base
    }
    .add-alert {
        @apply flex text-sm items-center whitespace-nowrap gap-1.5 px-3 w-fit py-2 text-teal-600 border border-teal-600/20 rounded font-medium bg-transparent hover:bg-transparent cursor-pointer transition-colors;
    }
    .watchlist-news {
        @apply grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4;
    }
    .news-item {
        @apply bg-gray-800 rounded-lg border w-full border-gray-600  p-4 duration-200 hover:border-gray-600 cursor-pointer;
    }
    .news-tag {
        @apply inline-block w-fit px-2 py-1 mb-5 rounded bg-gray-600/60 text-green-500 text-sm font-mono font-medium;
    }
    .news-title {
        @apply text-lg  font-semibold text-gray-100 leading-tight mb-3 line-clamp-2;
    }
    .news-meta {
        @apply flex items-center text-sm text-gray-500 mb-1;
    }
    .news-summary {
        @apply text-gray-400 flex-1 text-base leading-relaxed mb-3 line-clamp-3;
    }
    .news-cta {
        @apply text-sm align-bottom text-teal-500 hover:text-gray-400;
    }
    .alert-dialog {
        @apply bg-gray-800 border-gray-600 text-gray-400 max-w-md;
    }
    .alert-title {
        @apply text-xl font-semibold text-gray-100;
    }
    .alert-list {
        @apply overflow-y-auto w-full max-h-[911px] rounded-lg flex border border-gray-600 flex-col gap-4 bg-gray-800 p-3 flex-1;
    }
    .alert-empty {
        @apply px-6 py-8 text-center text-gray-500/50;
    }
    .alert-item {
        @apply p-4 rounded-lg bg-gray-700 border border-gray-600;
    }
    .alert-name {
        @apply mb-2 text-lg text-teal-500 font-semibold;
    }
    .alert-details {
        @apply flex border-b pb-3 items-center justify-between gap-3 mb-2;
    }
    .alert-company {
        @apply text-gray-400 text-base;
    }
    .alert-price {
        @apply text-gray-100 font-bold;
    }
    .alert-actions {
        @apply flex items-end justify-between;
    }
    .alert-update-btn {
        @apply text-gray-400 rounded-full bg-transparent hover:bg-green-500/15 cursor-pointer;
    }
    .alert-delete-btn {
        @apply text-gray-400 rounded-full hover:bg-red-600/15 bg-transparent cursor-pointer transition-colors;
    }
}

/* Market News Component Styles */
.scrollbar-hide {
    -ms-overflow-style: none;
    scrollbar-width: none;
}

.scrollbar-hide::-webkit-scrollbar {
    display: none;
}

/* TradingView Advanced Chart Widget Styles */
.tradingview-widget-container {
    position: relative;
    background-color: #141414 !important;
    border-radius: 8px !important;
    overflow: hidden !important;
}

.tv-embed-widget-wrapper__body {
    background-color: #141414 !important;
}

.tradingview-widget-container__widget {
    background-color: #141414 !important;
    height: 100% !important;

}

.widget-stock-heatmap-container .screenerMapWrapper-BBVfGP0b {
    overflow: hidden !important;
    background: #141414 !important;
    background-color: #141414 !important;
}

.canvasContainer-tyaAU8aH {
    background: #141414 !important;
    background-color: #141414 !important;
}

.tv-site-widget--bg_none {
    background-color: transparent !important;
}

.tradingview-widget-copyright {
    font-size: 11px;
    color: #9ca3af;
    text-align: center;
    padding: 4px 0;
    background-color: transparent;
}

.tradingview-widget-copyright a {
    color: #60a5fa;
    text-decoration: none;
    transition: color 0.2s ease;
}

.tradingview-widget-copyright a:hover {
    color: #93c5fd;
    text-decoration: underline;
}

.tv-embed-widget-wrapper .tv-embed-widget-wrapper__body {
    background: #141414 !important;
    background-color: #141414 !important;
}

.tradingview-widget-container iframe {
    background-color: #141414 !important;
    width: 100% !important;
}

.custom-chart.tradingview-widget-container iframe {
    border: 1px solid #30333A;
    border-radius: 8px !important;
    overflow: hidden !important;
}

/* Custom scrollbar that shows on hover */
.scrollbar-hide-default {
    scrollbar-width: thin;
    scrollbar-color: transparent transparent;
}

.scrollbar-hide-default::-webkit-scrollbar {
    width: 8px;
}

.scrollbar-hide-default::-webkit-scrollbar-track {
    background: transparent;
}

.scrollbar-hide-default::-webkit-scrollbar-thumb {
    background-color: transparent;
    border-radius: 4px;
    transition: background-color 0.3s ease;
}

.scrollbar-hide-default:hover {
    scrollbar-color: #30333A transparent;
}

.scrollbar-hide-default:hover::-webkit-scrollbar-thumb {
    background-color: #30333A;
}

.scrollbar-hide-default::-webkit-scrollbar-thumb:hover {
    background-color: #9095A1;
}

================================================
FILE: app/layout.tsx
================================================
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import { Analytics } from "@vercel/analytics/next";
import {Toaster} from "@/components/ui/sonner";
import "./globals.css";

const geistSans = Geist({
  variable: "--font-geist-sans",
  subsets: ["latin"],
});

const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"],
});

export const metadata: Metadata = {
  title: "OpenStock",
  description: "OpenStock is an open-source alternative to expensive market platforms. Track real-time prices, set personalized alerts, and explore detailed company insights — built openly, for everyone, forever free.",
};

export default function RootLayout({
                                       children,
                                   }: Readonly<{
    children: React.ReactNode;
}>) {
    return (
        <html lang="en" className="dark">
            <body
                className={`${geistSans.variable} ${geistMono.variable} antialiased`}
            >
                {children}
                <Toaster/>
                <Analytics />
            </body>
        </html>
    );
}


================================================
FILE: components/DonatePopup.tsx
================================================
'use client';

import React, { useEffect, useState } from 'react';
import {
    Dialog,
    DialogContent,
    DialogDescription,
    DialogHeader,
    DialogTitle,
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Heart, Github } from 'lucide-react';

const DONATE_POPUP_KEY = 'opendevsociety-donate-popup-dismissed';
const DONATE_POPUP_DELAY = 3000; // Show after 3 seconds
const DONATE_POPUP_COOLDOWN = 24 * 60 * 60 * 1000; // 24 hours in milliseconds

const GITHUB_SPONSOR_URL = 'https://github.com/sponsors/ravixalgorithm';

export default function DonatePopup() {
    const [open, setOpen] = useState(false);

    useEffect(() => {
        // Check if user has dismissed popup
        const dismissed = localStorage.getItem(DONATE_POPUP_KEY);
        
        if (dismissed) {
            const dismissedTime = parseInt(dismissed, 10);
            const now = Date.now();
            // Show again after cooldown period
            if (now - dismissedTime < DONATE_POPUP_COOLDOWN) {
                return;
            }
        }

        // Show popup after delay
        const timer = setTimeout(() => {
            setOpen(true);
        }, DONATE_POPUP_DELAY);

        return () => clearTimeout(timer);
    }, []);

    // Listen for custom event from donate button
    useEffect(() => {
        const handleOpenPopup = () => setOpen(true);
        window.addEventListener('open-donate-popup', handleOpenPopup);
        return () => window.removeEventListener('open-donate-popup', handleOpenPopup);
    }, []);

    const handleDismiss = () => {
        setOpen(false);
        // Store dismissal time
        localStorage.setItem(DONATE_POPUP_KEY, Date.now().toString());
    };

    const handleDonate = () => {
        window.open(GITHUB_SPONSOR_URL, '_blank', 'noopener,noreferrer');
        handleDismiss();
    };

    return (
        <Dialog open={open} onOpenChange={setOpen}>
            <DialogContent className="!bg-gray-800 !border-teal-600/50 text-gray-100 max-w-md mx-4 sm:mx-auto sm:w-full sm:max-w-lg">
                <DialogHeader>
                    <div className="flex items-center gap-3 mb-2">
                        <div className="p-2 bg-teal-500/20 rounded-lg">
                            <Heart className="h-6 w-6 text-teal-400 fill-teal-400" />
                        </div>
                        <DialogTitle className="text-2xl font-bold text-gray-100">
                            Keep OpenStock Free
                        </DialogTitle>
                    </div>
                    <DialogDescription className="text-gray-400 text-base leading-relaxed pt-2">
                        Your overwhelming love for OpenStock and Open Dev Society has helped us grow, 
                        but we're hitting Vercel's free tier limits. 
                        <br /><br />
                        Help us keep OpenStock free and accessible for everyone by supporting us on GitHub Sponsors. 
                        Every contribution, no matter how small, makes a difference! 💙
                    </DialogDescription>
                </DialogHeader>

                <div className="flex flex-col sm:flex-row gap-3 mt-6">
                    <Button
                        onClick={handleDonate}
                        className="flex-1 bg-gradient-to-r from-teal-500 to-cyan-500 hover:from-teal-600 hover:to-cyan-600 text-white font-semibold h-11 transition-all duration-200 transform hover:scale-105"
                    >
                        <Github className="h-4 w-4 mr-2" />
                        Sponsor on GitHub
                    </Button>
                    <Button
                        onClick={handleDismiss}
                        variant="outline"
                        className="flex-1 border-teal-600/50 text-teal-400 hover:bg-teal-600/10 hover:text-teal-300 h-11 transition-all duration-200"
                    >
                        Maybe Later
                    </Button>
                </div>

                <p className="text-xs text-gray-500 text-center mt-4">
                    This popup won't appear again for 24 hours after dismissing
                </p>
            </DialogContent>
        </Dialog>
    );
}


================================================
FILE: components/Footer.tsx
================================================
import Link from "next/link";
import Image from "next/image";
import OpenDevSocietyBranding from "./OpenDevSocietyBranding";

const Footer = () => {
    return (
        <footer className="bg-gray-900 text-white border-t border-gray-800">
            <div className="container mx-auto px-4 py-12">
                <div className="grid grid-cols-1 md:grid-cols-3 gap-8">
                    {/* Brand Section */}
                    <div className="col-span-1 md:col-span-2">
                        <Link href="/" className="flex items-center gap-2 mb-4">
                            <Image
                                src="/assets/images/logo.png"
                                alt="OpenStock"
                                width={150}
                                height={38}
                                className="brightness-0 invert"
                            />
                        </Link>
                        <p className="text-gray-400 mb-6 max-w-md">
                            OpenStock is an open-source alternative to expensive market platforms. Track real-time prices, set personalized alerts, and explore detailed company insights — built openly, for everyone, forever free.
                        </p>
                        <div className="mb-8">
                            <Link href="/about" className="text-teal-400 hover:text-teal-300 font-medium inline-flex items-center gap-1 group">
                                Learn about our mission
                                <span className="group-hover:translate-x-1 transition-transform">→</span>
                            </Link>
                        </div>
                        <div className="flex space-x-6">
                            <Link
                                href="https://github.com/Open-Dev-Society/OpenStock"
                                target="_blank"
                                rel="noopener noreferrer"
                                className="text-gray-400 hover:text-white transition-colors duration-200 relative group"
                            >
                                <span className="relative">
                                    GitHub
                                    <span className="absolute left-0 bottom-0 w-0 h-0.5 bg-white transition-all duration-300 group-hover:w-full"></span>
                                </span>
                            </Link>
                            <Link
                                href="https://www.linkedin.com/company/opendevsociety-in/"
                                target="_blank"
                                rel="noopener noreferrer"
                                className="text-gray-400 hover:text-blue-400 transition-colors duration-200 relative group"
                            >
                                <span className="relative">
                                    LinkedIn
                                    <span className="absolute left-0 bottom-0 w-0 h-0.5 bg-blue-400 transition-all duration-300 group-hover:w-full"></span>
                                </span>
                            </Link>
                            <Link
                                href="https://discord.gg/JkJ8kfxgxB"
                                target="_blank"
                                rel="noopener noreferrer"
                                className="text-gray-400 hover:text-blue-600 transition-colors duration-200 relative group"
                            >
                                <span className="relative">
                                    Discord
                                    <span className="absolute left-0 bottom-0 w-0 h-0.5 bg-blue-600 transition-all duration-300 group-hover:w-full"></span>
                                </span>
                            </Link>
                        </div>
                    </div>

                    {/* Resources */}
                    <div>
                        <h3 className="text-lg font-semibold mb-4">Resources</h3>
                        <ul className="space-y-2">
                            <li>
                                <Link href="/api-docs" className="text-gray-400 hover:text-white transition-colors duration-200 relative group">
                                    <span className="relative">
                                        API Documentation
                                        <span className="absolute left-0 bottom-0 w-0 h-0.5 bg-white transition-all duration-300 group-hover:w-full"></span>
                                    </span>
                                </Link>
                            </li>
                            <li>
                                <Link href="/help" className="text-gray-400 hover:text-white transition-colors duration-200 relative group">
                                    <span className="relative">
                                        Help Center
                                        <span className="absolute left-0 bottom-0 w-0 h-0.5 bg-white transition-all duration-300 group-hover:w-full"></span>
                                    </span>
                                </Link>
                            </li>
                            <li>
                                <Link href="/terms" className="text-gray-400 hover:text-white transition-colors duration-200 relative group">
                                    <span className="relative">
                                        Terms of Service
                                        <span className="absolute left-0 bottom-0 w-0 h-0.5 bg-white transition-all duration-300 group-hover:w-full"></span>
                                    </span>
                                </Link>
                            </li>
                        </ul>
                    </div>
                </div>

                {/* Divider */}
                <div className="border-t border-gray-800 mt-8 pt-8">
                    <div className="flex flex-col md:flex-row justify-between items-center">
                        {/* Copyright */}
                        <div className="text-gray-400 text-sm mb-4 md:mb-0">
                            © {new Date().getFullYear()} Open Dev Society. All rights reserved.
                        </div>

                        {/* Open Dev Society Branding */}
                        <div className="flex items-center space-x-2">
                            <OpenDevSocietyBranding />
                        </div>
                    </div>
                </div>
            </div>
        </footer>
    );
};

export default Footer;


================================================
FILE: components/Header.tsx
================================================
import Link from "next/link";
import Image from "next/image";
import NavItems from "@/components/NavItems";
import UserDropdown from "@/components/UserDropdown";
import {searchStocks} from "@/lib/actions/finnhub.actions";

const Header = async ({ user }: { user: User }) => {
    const initialStocks = await searchStocks();

    return (
        <header className="sticky top-0 header">
            <div className="container header-wrapper">
                <Link href="/" className="flex items-center justify-center gap-2">
                    <Image
                        src="/assets/images/logo.png"
                        alt="OpenStock"
                        width={200}
                        height={50}
                    />
                </Link>
                <nav className="hidden sm:block">
                    <NavItems initialStocks={initialStocks}/>
                </nav>

                <UserDropdown user={user} initialStocks={initialStocks} />
            </div>
        </header>
    )
}
export default Header

================================================
FILE: components/NavItems.tsx
================================================
'use client'


import React, { createContext, useContext } from 'react'
import {NAV_ITEMS} from "@/lib/constants";
import Link from "next/link";
import {usePathname} from "next/navigation";
import SearchCommand from "@/components/SearchCommand";
import { Heart } from 'lucide-react';
import { Button } from '@/components/ui/button';

// Create context for popup state
const DonatePopupContext = createContext<{
    openDonatePopup: () => void;
}>({
    openDonatePopup: () => {}
});

export const useDonatePopup = () => useContext(DonatePopupContext);

const NavItems = ({initialStocks}: { initialStocks: StockWithWatchlistStatus[]}) => {
    const pathname = usePathname()

    const isActive = (path: string) => {
        if (path ==='/') return pathname === '/'

        return  pathname.startsWith(path);
    }

    const openDonatePopup = () => {
        // Trigger the popup by dispatching a custom event
        window.dispatchEvent(new CustomEvent('open-donate-popup'));
    }

    return (
        <DonatePopupContext.Provider value={{ openDonatePopup }}>
            <ul className="flex flex-col sm:flex-row p-2 gap-3 sm:gap-10 font-medium">
            {NAV_ITEMS.map(({href, label}) => {
                if (href === '/search') return (
                    <li key="search-trigger">
                        <SearchCommand
                            renderAs="text"
                            label="Search"
                            initialStocks={initialStocks}
                        />
                    </li>
                )
                return <li key={href}>
                    <Link href={href} className={`hover:text-teal-500 transition-colors ${isActive(href) ? 'text-gray-100' : ''}`}>
                        {label}
                    </Link>
                </li>
            })}
            <li key="donate">
                <Button
                    onClick={openDonatePopup}
                    className="bg-gradient-to-r from-teal-500 to-cyan-500 hover:from-teal-600 hover:to-cyan-600 text-white font-semibold px-4 py-2 rounded-lg shadow-lg hover:shadow-xl transition-all duration-200 transform hover:scale-105 flex items-center gap-2 animate-pulse"
                    size="sm"
                >
                    <Heart className="h-4 w-4 fill-current" />
                    Donate
                </Button>
            </li>
        </ul>
        </DonatePopupContext.Provider>
    )
}
export default NavItems


================================================
FILE: components/OpenDevSocietyBranding.tsx
================================================
import React from "react";

// SVG version of your logo (image 2)
// Replace with real SVG for sharpest results; this is an inline approximation
const ODSLogoSVG: React.FC<{ size?: number }> = ({ size = 26 }) => (

    <svg
        viewBox="0 0 500 500"
        width={size}
        height={size}
        xmlns="http://www.w3.org/2000/svg"
        style={{ display: "block" }}
    >
    <path d="M0 0 C-0.59168331 6.13519237 -1.91811379 11.68664935 -3.75 17.5625 C-4.01949463 18.44526611 -4.28898926 19.32803223 -4.56665039 20.23754883 C-6.10823873 25.25055226 -7.73657889 30.22600918 -9.4375 35.1875 C-11.80231453 42.2006531 -13.80787025 49.28689421 -15.77880859 56.41967773 C-19.13472536 68.55037635 -22.59990826 80.6493947 -26.0625 92.75 C-26.65814106 94.83251401 -27.25376312 96.91503345 -27.84936523 98.99755859 C-28.13946976 100.01123505 -28.42957428 101.0249115 -28.72846985 102.06930542 C-29.30502052 104.08585119 -29.88046135 106.10271462 -30.45475769 108.11990356 C-31.86556075 113.06957368 -33.29618588 118.01176819 -34.76904297 122.94335938 C-35.04928818 123.89261292 -35.32953339 124.84186646 -35.61827087 125.81988525 C-36.14436253 127.5996549 -36.67766676 129.37731594 -37.21974182 131.15228271 C-39.66178754 139.33771563 -39.66178754 139.33771563 -37.7421875 147.37890625 C-37.12601562 148.09949219 -36.50984375 148.82007813 -35.875 149.5625 C-35.15183594 150.41070312 -34.42867188 151.25890625 -33.68359375 152.1328125 C-30.52901994 155.5032014 -27.24168535 158.71504516 -23.9375 161.9375 C-18.29203085 167.46150512 -13.65540443 172.92711687 -10 180 C-9.60554688 180.76054687 -9.21109375 181.52109375 -8.8046875 182.3046875 C-3.65791821 193.62757993 -2.03570319 206.79702334 -4 219 C-4.11472656 219.86367188 -4.22945313 220.72734375 -4.34765625 221.6171875 C-6.5528062 236.04700369 -11.82160653 248.44519661 -19 261 C-19.79192848 262.52069702 -20.58363581 264.04150925 -21.375 265.5625 C-27.23437913 275.95313232 -34.64075445 285.07452454 -42.84765625 293.671875 C-45.01666666 295.80100516 -45.01666666 295.80100516 -46 298 C-46.66 298 -47.32 298 -48 298 C-48 298.66 -48 299.32 -48 300 C-57.42443714 310.02236216 -70.14592008 318.22438575 -82 325 C-82.6285791 325.36480469 -83.2571582 325.72960937 -83.90478516 326.10546875 C-99.61585129 335.14270532 -116.41222833 340.81040989 -134 345 C-134.82886719 345.20238281 -135.65773438 345.40476562 -136.51171875 345.61328125 C-138.9999159 346.16668333 -141.4823203 346.60059472 -144 347 C-145.11632812 347.185625 -146.23265625 347.37125 -147.3828125 347.5625 C-169.51797618 350.81228393 -193.91685742 350.65499375 -215.88476562 346.40576172 C-217.86931828 346.02506845 -219.86430817 345.6999747 -221.859375 345.37890625 C-238.86371195 342.44146178 -254.9288229 336.29970145 -270 328 C-271.05445313 327.43539063 -272.10890625 326.87078125 -273.1953125 326.2890625 C-308.75641659 306.51226049 -332.52233798 272.07913512 -345 234 C-345.58201172 232.23462891 -345.58201172 232.23462891 -346.17578125 230.43359375 C-355.31166394 200.55678818 -356.78504504 170.67956678 -352 140 C-351.89316895 139.29101562 -351.78633789 138.58203125 -351.67626953 137.8515625 C-345.25187659 96.49914815 -320.82714377 57.99080566 -287.30078125 33.265625 C-281.40831565 29.15452021 -275.27918672 25.4854336 -269 22 C-268.37786621 21.65195313 -267.75573242 21.30390625 -267.11474609 20.9453125 C-224.87569822 -2.34691188 -169.33419942 -4.92301213 -123.31494141 7.73876953 C-101.32801804 14.2200043 -80.63678375 24.20469808 -63 39 C-63.40931094 43.88503711 -65.81475771 46.35924649 -69.125 49.6875 C-74.00348672 54.78820341 -78.1365624 60.07886193 -82 66 C-82.81215558 67.20856485 -83.62459789 68.41693715 -84.4375 69.625 C-85.2109375 70.800625 -85.2109375 70.800625 -86 72 C-89.93981221 70.68672926 -90.20174819 69.23922683 -92.125 65.625 C-104.76880006 43.2531609 -124.92353146 27.66554317 -149.5 20.625 C-160.72427433 17.92352396 -172.45150148 16.44029894 -184 17 C-184.93199219 17.01804687 -185.86398438 17.03609375 -186.82421875 17.0546875 C-207.66145317 17.9436034 -225.82271857 26.06981798 -242.42578125 38.21484375 C-245 40 -245 40 -248 41 C-248 41.66 -248 42.32 -248 43 C-249.57421875 44.515625 -249.57421875 44.515625 -251.6875 46.25 C-254.35349133 48.50075728 -256.71010091 50.70049545 -258.875 53.4375 C-259.926875 54.7059375 -259.926875 54.7059375 -261 56 C-261.66 56 -262.32 56 -263 56 C-263.23589844 56.53238281 -263.47179687 57.06476563 -263.71484375 57.61328125 C-265.03805671 60.07067674 -266.6010465 62.23558473 -268.27441406 64.46459961 C-280.42779576 80.72055387 -288.02178014 98.87531339 -293.75 118.25 C-294.04406738 119.24394775 -294.33813477 120.23789551 -294.64111328 121.26196289 C-299.77610307 139.17198664 -301.90249553 158.3876039 -301 177 C-300.95891113 177.85368164 -300.91782227 178.70736328 -300.87548828 179.58691406 C-298.66952922 220.39363275 -286.70505657 259.38868413 -262.18579102 292.25512695 C-260.9318278 294.10031433 -259.95421018 295.98580178 -259 298 C-258.34 298 -257.68 298 -257 298 C-257 298.66 -257 299.32 -257 300 C-256.34 300 -255.68 300 -255 300 C-253.31267723 301.98260426 -251.64633943 303.9832342 -250 306 C-249.34 306.33 -248.68 306.66 -248 307 C-248 307.66 -248 308.32 -248 309 C-247.46117187 309.24363281 -246.92234375 309.48726563 -246.3671875 309.73828125 C-243.4176446 311.31039735 -240.83190615 313.22651188 -238.125 315.1875 C-215.28302586 331.2221776 -188.61315222 336.83850869 -161.16015625 332.39794922 C-132.95843071 327.202559 -108.71485132 310.22003019 -92.28808594 286.93505859 C-78.08016427 265.59086088 -70.59668887 241.83540587 -63.91308594 217.33740234 C-62.35886022 211.65658267 -60.77289066 205.98468476 -59.1875 200.3125 C-58.8975415 199.26771484 -58.60758301 198.22292969 -58.30883789 197.14648438 C-57.21918287 193.23875603 -56.13066152 189.37422473 -54.79296875 185.54296875 C-53.27332879 180.66964062 -54.17164969 176.58767143 -56.5 172.125 C-61.27813553 165.75990249 -66.91164558 160.01802606 -72.625 154.5 C-84.43252384 142.77461546 -91.82172648 128.52507497 -92.375 111.875 C-92.26072847 103.73723444 -90.53309032 96.72283634 -88 89 C-87.72285156 88.12988281 -87.44570313 87.25976562 -87.16015625 86.36328125 C-80.51023971 67.82319137 -65.13014317 48.24222099 -49 37 C-48.34 37 -47.68 37 -47 37 C-47 36.34 -47 35.68 -47 35 C-44.99481947 33.27213167 -42.98102715 31.65971807 -40.875 30.0625 C-39.58060321 29.06396534 -38.28633588 28.06526284 -36.9921875 27.06640625 C-35.99396973 26.30094482 -35.99396973 26.30094482 -34.97558594 25.52001953 C-31.96503027 23.20369235 -28.98317476 20.85140869 -26 18.5 C-24.84778579 17.59488715 -23.69544114 16.68994034 -22.54296875 15.78515625 C-20.06401467 13.8376544 -17.58793642 11.88655486 -15.11328125 9.93359375 C-14.47712891 9.43214844 -13.84097656 8.93070313 -13.18554688 8.4140625 C-11.96608031 7.45273119 -10.74729439 6.49053565 -9.52929688 5.52734375 C-2.51318941 0 -2.51318941 0 0 0 Z " fill="#28868A" transform="translate(429,75)"/>
    <path d="M0 0 C0.99 0.99 0.99 0.99 2 2 C1.91796875 4.484375 1.91796875 4.484375 1 7 C-1.07421875 8.265625 -1.07421875 8.265625 -3.6875 9.25 C-9.39231619 11.84086924 -12.45789347 15.28729565 -15 21 C-16.05583886 25.9462847 -16.14929111 30.73550856 -16.17578125 35.765625 C-16.19898493 37.4778768 -16.22310249 39.19011642 -16.24804688 40.90234375 C-16.2830454 43.57251107 -16.31246081 46.24222952 -16.32592773 48.91259766 C-16.41195121 63.21366975 -17.35955035 73.00332002 -27.60546875 83.79296875 C-30.84843162 86.78199652 -34.13355105 88.89102784 -38 91 C-37.35933594 91.4125 -36.71867187 91.825 -36.05859375 92.25 C-26.30369227 98.76295956 -19.66669937 105.25792053 -17 117 C-14.81034565 133.84494245 -12.82028186 154.22651672 -22.625 168.875 C-26.29556131 173.55649225 -30.43381359 177.67252166 -36 180 C-36.99 179.67 -37.98 179.34 -39 179 C-39.00364563 178.25577286 -39.00729126 177.51154572 -39.01104736 176.74476624 C-39.04906448 169.69826617 -39.1060711 162.6522274 -39.18390274 155.60607243 C-39.22325196 151.98435331 -39.2555012 148.36287275 -39.27099609 144.7409668 C-39.28624655 141.23932697 -39.32083721 137.73834353 -39.36830902 134.2369976 C-39.39015817 132.26479487 -39.3924665 130.29240648 -39.39434814 128.32008362 C-39.5144712 120.88444796 -40.47326327 115.53159109 -44 109 C-44.33 108.01 -44.66 107.02 -45 106 C-47.39068734 104.89363605 -47.39068734 104.89363605 -50 104 C-50.99 103.34 -51.98 102.68 -53 102 C-56.11677416 101.39184894 -59.2359528 100.98509986 -62.3828125 100.5625 C-63.67832031 100.2840625 -63.67832031 100.2840625 -65 100 C-66.50156874 96.99686252 -66.09281204 94.20932478 -66.0625 90.875 C-66.05347656 89.59367187 -66.04445312 88.31234375 -66.03515625 86.9921875 C-66.02355469 86.00476563 -66.01195312 85.01734375 -66 84 C-64.5459375 83.5978125 -64.5459375 83.5978125 -63.0625 83.1875 C-61.70751638 82.7944723 -60.35342624 82.39835763 -59 82 C-58.24332031 81.78214844 -57.48664063 81.56429687 -56.70703125 81.33984375 C-51.33207019 79.69224262 -46.87616896 78.14178802 -43.84765625 73.0703125 C-40.89441014 67.33197664 -39.86052288 62.64015298 -39.82421875 56.18359375 C-39.80099854 54.59829434 -39.77688035 53.01300787 -39.75195312 51.42773438 C-39.71703753 48.94189053 -39.6876035 46.45651667 -39.67407227 43.97045898 C-39.59560369 31.89957777 -38.75851534 21.82745276 -31 12 C-30.34 12 -29.68 12 -29 12 C-28.67 11.01 -28.34 10.02 -28 9 C-20.6137012 2.26285335 -9.93037816 -1.07938893 0 0 Z " fill="#C93E5E" transform="translate(238,159)"/>
    <path d="M0 0 C0.99 0.33 1.98 0.66 3 1 C3.00586624 1.73391968 3.01173248 2.46783936 3.01777649 3.22399902 C3.07587356 10.1677239 3.14804682 17.11117805 3.23571491 24.05458832 C3.28028966 27.62360846 3.31957324 31.19251786 3.34643555 34.76171875 C3.37259851 38.21139862 3.4130901 41.66064928 3.46318626 45.11006165 C3.48779316 47.05516814 3.49786607 49.00044115 3.50764465 50.94567871 C3.61875194 57.68503806 4.13547328 62.87679918 7 69 C7.474375 70.051875 7.94875 71.10375 8.4375 72.1875 C13.8718728 78.15205552 22.47128818 79.84893948 30 82 C30 86.95 30 91.9 30 97 C22.55319149 99.12765957 22.55319149 99.12765957 18.8125 99.9375 C15.34638123 100.90346753 13.67203184 101.73904998 11 104 C10.34 104 9.68 104 9 104 C5.33894411 110.75887242 3.56426117 116.6272 3.6640625 124.234375 C3.65154507 125.79427495 3.63850772 127.35417081 3.625 128.9140625 C3.62109034 131.32730056 3.6249398 133.74012247 3.63964844 136.15332031 C3.68800746 149.2801084 2.37806232 160.38367233 -7.1875 170.3203125 C-12.67043244 175.40147834 -17.82480592 177.9419986 -25 180 C-25.99 180.33 -26.98 180.66 -28 181 C-30.6928496 181.17812624 -33.29441368 181.08727698 -36 181 C-37 180 -37 180 -37.0625 176.9375 C-37.041875 175.968125 -37.02125 174.99875 -37 174 C-34.03 173.01 -31.06 172.02 -28 171 C-28 170.34 -28 169.68 -28 169 C-27.236875 168.319375 -26.47375 167.63875 -25.6875 166.9375 C-20.19133108 160.93005955 -19.57166079 153.65457195 -19.6640625 145.79296875 C-19.65154837 144.05403274 -19.63851118 142.31510043 -19.625 140.57617188 C-19.62108479 137.88422242 -19.62497194 135.19264606 -19.63964844 132.50073242 C-19.68474643 118.81725042 -18.85551365 107.14249679 -8.90625 96.69140625 C-1.36489607 90 -1.36489607 90 3 90 C3 89.34 3 88.68 3 88 C1.7625 87.7215625 1.7625 87.7215625 0.5 87.4375 C-8.92434362 83.56678744 -14.23775972 75.47730909 -18.12109375 66.37109375 C-19.3560643 61.63429559 -19.51690735 57.00347023 -19.6875 52.125 C-19.72512451 51.10575439 -19.76274902 50.08650879 -19.80151367 49.03637695 C-20.69937108 20.29363182 -20.69937108 20.29363182 -10.625 8.625 C-7.32371282 5.17710961 -4.28243613 2.14121807 0 0 Z " fill="#C73E5D" transform="translate(298,161)"/>
    <path d="M0 0 C2.90387376 2.57530089 4.61020091 5.54101117 6.5625 8.875 C9.91486726 14.49992782 13.66141487 19.67088486 17.76196289 24.77294922 C19.69560439 27.19736462 21.00872564 29.02617692 22 32 C22.66 32 23.32 32 24 32 C25.7265625 33.96875 25.7265625 33.96875 27.625 36.5 C28.25664063 37.3353125 28.88828125 38.170625 29.5390625 39.03125 C30.02117187 39.6809375 30.50328125 40.330625 31 41 C24.59079987 47.60948764 17.60747308 53.37035965 10.2890625 58.94262695 C4.55558076 63.31440678 -1.06695233 67.82044666 -6.6875 72.3359375 C-7.25210938 72.78807617 -7.81671875 73.24021484 -8.3984375 73.70605469 C-9.88279425 74.90530633 -11.34852327 76.12751529 -12.8125 77.3515625 C-15 79 -15 79 -17 79 C-17 79.66 -17 80.32 -17 81 C-19.475 81.99 -19.475 81.99 -22 83 C-21.49815993 75.01536602 -19.37197136 67.62975364 -17.1640625 59.98748779 C-12.68237925 44.46512844 -8.62878916 28.86328659 -4.86914062 13.15112305 C-4.51696369 11.7023023 -4.16475205 10.25348999 -3.8125 8.8046875 C-3.35810547 6.90178955 -3.35810547 6.90178955 -2.89453125 4.96044922 C-2 2 -2 2 0 0 Z " fill="#2A8589" transform="translate(92,343)"/>
    <path d="M0 0 C0.66 0.66 1.32 1.32 2 2 C1.91796875 4.484375 1.91796875 4.484375 1 7 C-1.07421875 8.265625 -1.07421875 8.265625 -3.6875 9.25 C-9.41725859 11.84398813 -12.36731741 15.3266176 -15 21 C-15.66411298 23.67528169 -15.66411298 23.67528169 -16 26 C-16.33 26 -16.66 26 -17 26 C-17.309375 24.906875 -17.61875 23.81375 -17.9375 22.6875 C-18.58337897 20.44592006 -19.2623117 18.2130649 -20 16 C-18.0625 13.3125 -18.0625 13.3125 -16 11 C-16.66 11 -17.32 11 -18 11 C-18.33 9.68 -18.66 8.36 -19 7 C-18.34 7 -17.68 7 -17 7 C-17.33 5.68 -17.66 4.36 -18 3 C-12.0858622 0.39082156 -6.45343916 -0.3723138 0 0 Z " fill="#BF3F5D" transform="translate(238,159)"/>
    <path d="M0 0 C0.33 0 0.66 0 1 0 C1 2.97 1 5.94 1 9 C-0.32 9.33 -1.64 9.66 -3 10 C-2.814375 10.556875 -2.62875 11.11375 -2.4375 11.6875 C-1.92448835 14.399133 -2.3359276 16.34371039 -3 19 C-1.68 19.33 -0.36 19.66 1 20 C0.67 20.99 0.34 21.98 0 23 C-5.47200022 24.82400007 -10.25539359 25.30234771 -16 25 C-17 24 -17 24 -17.0625 20.9375 C-17.041875 19.968125 -17.02125 18.99875 -17 18 C-14.03 17.01 -11.06 16.02 -8 15 C-8 14.34 -8 13.68 -8 13 C-7.236875 12.29875 -6.47375 11.5975 -5.6875 10.875 C-2.39141811 7.34895891 -1.46719365 4.55602238 0 0 Z " fill="#C44162" transform="translate(278,317)"/>
    <path d="M0 0 C0 0.66 0 1.32 0 2 C0.67546875 1.97679688 1.3509375 1.95359375 2.046875 1.9296875 C3.38492188 1.90261719 3.38492188 1.90261719 4.75 1.875 C6.07257813 1.84019531 6.07257813 1.84019531 7.421875 1.8046875 C10.1401489 2.01061734 11.63096259 2.72741299 14 4 C12.93676514 3.93941406 11.87353027 3.87882812 10.77807617 3.81640625 C6.78426986 3.58965685 2.79024335 3.36700468 -1.20385742 3.14550781 C-2.9233199 3.04959769 -4.64272308 2.95261774 -6.36206055 2.85449219 C-19.07729109 2.11281354 -19.07729109 2.11281354 -31.81054688 2.01367188 C-35.26169658 1.99212143 -38.59068192 1.51461405 -42 1 C-42 0.67 -42 0.34 -42 0 C-28.23620317 -1.76782712 -13.76379683 -1.76782712 0 0 Z " fill="#347C7E" transform="translate(271,77)"/>
    <path d="M0 0 C0.33 0 0.66 0 1 0 C2.19388403 37.45176103 2.19388403 37.45176103 2 56 C2.99 56.33 3.98 56.66 5 57 C3.0625 57.5625 3.0625 57.5625 1 58 C0 57 0 57 -0.12025452 54.71580505 C-0.11803391 53.70542679 -0.11581329 52.69504852 -0.11352539 51.65405273 C-0.11344986 50.5129454 -0.11337433 49.37183807 -0.11329651 48.19615173 C-0.10813522 46.9545076 -0.10297394 45.71286346 -0.09765625 44.43359375 C-0.0962413 43.16983414 -0.09482635 41.90607452 -0.09336853 40.60401917 C-0.08870582 37.23875111 -0.0797148 33.8735501 -0.06866455 30.50830078 C-0.05844846 27.0765033 -0.05387049 23.64469891 -0.04882812 20.21289062 C-0.0377738 13.47524176 -0.0210171 6.73762475 0 0 Z " fill="#69133D" transform="translate(199,281)"/>
    <path d="M0 0 C0.33 0.66 0.66 1.32 1 2 C2.32 1.67 3.64 1.34 5 1 C4.34 2.98 3.68 4.96 3 7 C3.99 6.34 4.98 5.68 6 5 C6.99 5 7.98 5 9 5 C9 5.66 9 6.32 9 7 C4.52941176 11 4.52941176 11 2 11 C2 11.66 2 12.32 2 13 C0.35 13.66 -1.3 14.32 -3 15 C-2.5547115 9.72684667 -1.91626837 4.98229776 0 0 Z " fill="#328385" transform="translate(73,411)"/>
    <path d="M0 0 C0.74426239 0.02028259 1.48852478 0.04056519 2.25534058 0.0614624 C3.82386858 0.10692937 5.39225114 0.15768228 6.96044922 0.21337891 C9.3653656 0.29681179 11.77016541 0.35852837 14.17578125 0.41796875 C15.70056128 0.4648481 17.22530338 0.51298195 18.75 0.5625 C19.47083771 0.58032532 20.19167542 0.59815063 20.93435669 0.61651611 C26.01204079 0.81672829 26.01204079 0.81672829 28.2421875 3.046875 C23.50993222 3.07169319 18.77771529 3.08979083 14.04541016 3.10180664 C12.43733052 3.10681875 10.8292554 3.11363019 9.22119141 3.12231445 C6.90265789 3.13451726 4.58418491 3.14013464 2.265625 3.14453125 C1.19651382 3.15227318 1.19651382 3.15227318 0.10580444 3.16017151 C-3.95637679 3.16062255 -7.77269356 2.83463107 -11.7578125 2.046875 C-8.00185758 -0.4248169 -4.32117188 -0.15627252 0 0 Z " fill="#236164" transform="translate(237.7578125,420.953125)"/>
    <path d="M0 0 C0.66 0 1.32 0 2 0 C2 11.88 2 23.76 2 36 C0.515 36.495 0.515 36.495 -1 37 C-0.67 24.79 -0.34 12.58 0 0 Z " fill="#90304B" transform="translate(199,189)"/>
    <path d="M0 0 C-0.33 2.64 -0.66 5.28 -1 8 C-1.556875 8.103125 -2.11375 8.20625 -2.6875 8.3125 C-5.11825733 8.91402865 -5.11825733 8.91402865 -7.375 10.5625 C-10.47577231 12.26054198 -12.51778564 12.24294519 -16 12 C-4.72727273 0 -4.72727273 0 0 0 Z " fill="#337F81" transform="translate(429,75)"/>
    <path d="M0 0 C0.66 0 1.32 0 2 0 C2.67310593 4.0614603 3.33735482 8.12432096 4 12.1875 C4.19078125 13.33798828 4.3815625 14.48847656 4.578125 15.67382812 C4.75859375 16.78564453 4.9390625 17.89746094 5.125 19.04296875 C5.29257813 20.06414795 5.46015625 21.08532715 5.6328125 22.13745117 C5.97414707 24.79845372 6.06884702 27.32142068 6 30 C5.34 30 4.68 30 4 30 C3.31661534 26.87842248 2.65651728 23.75232892 2 20.625 C1.80921875 19.75488281 1.6184375 18.88476562 1.421875 17.98828125 C0.16191376 11.91346811 -0.24024154 6.19789808 0 0 Z " fill="#2F7476" transform="translate(76,264)"/>
    <path d="M0 0 C0.33 0 0.66 0 1 0 C0.50903291 4.33687594 -0.09679957 8.59206158 -0.9375 12.875 C-2.43782056 20.85038823 -3.16583407 28.933438 -4 37 C-4.66 37 -5.32 37 -6 37 C-6.02689216 35.52093108 -6.04634621 34.04172517 -6.0625 32.5625 C-6.07410156 31.73878906 -6.08570312 30.91507813 -6.09765625 30.06640625 C-6 28 -6 28 -5 27 C-4.79068103 24.83703727 -4.63202931 22.6690529 -4.5 20.5 C-4.11111111 14.11111111 -4.11111111 14.11111111 -3 13 C-2.76712992 11.31816052 -2.58735834 9.6287584 -2.4375 7.9375 C-2.31181641 6.55884766 -2.31181641 6.55884766 -2.18359375 5.15234375 C-2.12300781 4.44207031 -2.06242188 3.73179687 -2 3 C-1.34 3 -0.68 3 0 3 C0 2.01 0 1.02 0 0 Z " fill="#35716F" transform="translate(132,197)"/>
    <path d="M0 0 C1.65 0 3.3 0 5 0 C1.19056924 4.14036587 -2.78593913 6.97238712 -7.625 9.8125 C-8.29273438 10.21919922 -8.96046875 10.62589844 -9.6484375 11.04492188 C-14.61495536 14 -14.61495536 14 -18 14 C-17.67 13.01 -17.34 12.02 -17 11 C-14.4375 9.8125 -14.4375 9.8125 -12 9 C-12 7.68 -12 6.36 -12 5 C-9.36 5.33 -6.72 5.66 -4 6 C-4 5.34 -4 4.68 -4 4 C-3.01 3.67 -2.02 3.34 -1 3 C-0.67 2.01 -0.34 1.02 0 0 Z " fill="#317779" transform="translate(357,389)"/>
    <path d="M0 0 C0.33 0.99 0.66 1.98 1 3 C-1.64738916 4.4606285 -2.89448334 5 -6 5 C-6.33 5.66 -6.66 6.32 -7 7 C-7.33 6.34 -7.66 5.68 -8 5 C-9.89533342 4.53476937 -9.89533342 4.53476937 -12.0625 4.375 C-13.361875 4.25125 -14.66125 4.1275 -16 4 C-16 3.34 -16 2.68 -16 2 C-10.54538831 0.61470179 -5.63281599 -0.2396943 0 0 Z " fill="#AC445B" transform="translate(238,159)"/>
    <path d="M0 0 C2.67927341 4.01891012 2.15601248 6.24161923 2 11 C2.66 11 3.32 11 4 11 C4 11.66 4 12.32 4 13 C4.85464844 13.04898437 5.70929688 13.09796875 6.58984375 13.1484375 C8.24693359 13.26058594 8.24693359 13.26058594 9.9375 13.375 C11.03964844 13.44460938 12.14179688 13.51421875 13.27734375 13.5859375 C14.62505859 13.79089844 14.62505859 13.79089844 16 14 C16.33 14.66 16.66 15.32 17 16 C5.68490113 16.2012567 5.68490113 16.2012567 1 15 C-0.44207941 12.11584117 -0.09394887 9.58278472 -0.0625 6.375 C-0.05347656 5.18648437 -0.04445312 3.99796875 -0.03515625 2.7734375 C-0.02355469 1.85820312 -0.01195312 0.94296875 0 0 Z " fill="#A23955" transform="translate(172,244)"/>
    <path d="M0 0 C0.66 0.33 1.32 0.66 2 1 C-2.75 6.875 -2.75 6.875 -5 8 C-5.65555119 10.52733235 -5.65555119 10.52733235 -6 13 C-7.65 13.33 -9.3 13.66 -11 14 C-11.33 12.68 -11.66 11.36 -12 10 C-8.04 6.7 -4.08 3.4 0 0 Z " fill="#327E81" transform="translate(410,88)"/>
    <path d="M0 0 C2.97 0 5.94 0 9 0 C9.66 1.32 10.32 2.64 11 4 C11.66 4.66 12.32 5.32 13 6 C10.93243723 8.7972908 9.85559629 9.02692273 6.3125 9.6875 C5.219375 9.790625 4.12625 9.89375 3 10 C4.375 8.5 4.375 8.5 6 7 C6.66 7 7.32 7 8 7 C8 6.34 8 5.68 8 5 C7.05125 4.731875 6.1025 4.46375 5.125 4.1875 C2 3 2 3 0 0 Z " fill="#BD3464" transform="translate(293,244)"/>
    <path d="M0 0 C0.99 0.33 1.98 0.66 3 1 C0.69 4.63 -1.62 8.26 -4 12 C-7 11 -7 11 -8.6875 8.125 C-10 5 -10 5 -10 2 C-9.01 2.33 -8.02 2.66 -7 3 C-7 4.65 -7 6.3 -7 8 C-6.34 8 -5.68 8 -5 8 C-4.731875 7.4225 -4.46375 6.845 -4.1875 6.25 C-2.98280141 3.9674132 -1.59201885 2.0262058 0 0 Z " fill="#327477" transform="translate(347,135)"/>
    <path d="M0 0 C3.63 0 7.26 0 11 0 C11 0.33 11 0.66 11 1 C-8.34042553 5.76595745 -8.34042553 5.76595745 -18 5 C-13.53362009 2.76681005 -10.96429512 1.88335856 -5.875 1.9375 C-4.77929688 1.94652344 -3.68359375 1.95554687 -2.5546875 1.96484375 C-1.71164062 1.97644531 -0.86859375 1.98804688 0 2 C0 1.34 0 0.68 0 0 Z " fill="#306B6A" transform="translate(292,417)"/>
    <path d="M0 0 C0 3.21812786 -0.05758039 4.4664092 -2 7 C-4.32156597 7.40729228 -6.6568787 7.74438677 -9 8 C-9.33 8.66 -9.66 9.32 -10 10 C-11.32 10 -12.64 10 -14 10 C-12.64412646 5.93237939 -10.0515406 5.13665748 -6.4375 3.3125 C-5.82197266 2.99216797 -5.20644531 2.67183594 -4.57226562 2.34179688 C-3.0526445 1.55253629 -1.52688189 0.77512085 0 0 Z " fill="#307D7F" transform="translate(166,94)"/>
    <path d="M0 0 C2.84562772 0.18263863 5.5609481 0.46831808 8.375 0.875 C9.90337717 1.08271569 11.43201724 1.28850781 12.9609375 1.4921875 C13.63946777 1.58870605 14.31799805 1.68522461 15.01708984 1.78466797 C17.33034225 2.07197283 17.33034225 2.07197283 21 2 C21.33 2.99 21.66 3.98 22 5 C13.65474478 5.51917486 6.08961486 3.95829294 -2 2 C-1.34 1.34 -0.68 0.68 0 0 Z " fill="#2B6F6E" transform="translate(203,417)"/>
    <path d="M0 0 C0.33 0 0.66 0 1 0 C1.33 1.65 1.66 3.3 2 5 C2.33 5 2.66 5 3 5 C3 10.61 3 16.22 3 22 C2.34 22 1.68 22 1 22 C0.67 23.32 0.34 24.64 0 26 C0 17.42 0 8.84 0 0 Z " fill="#A04F62" transform="translate(219,288)"/>
    <path d="M0 0 C0.66 0 1.32 0 2 0 C2 7.59 2 15.18 2 23 C1.01 23.33 0.02 23.66 -1 24 C-1.02893932 20.37501666 -1.04677792 16.75006287 -1.0625 13.125 C-1.07087891 12.09246094 -1.07925781 11.05992187 -1.08789062 9.99609375 C-1.09111328 9.00996094 -1.09433594 8.02382812 -1.09765625 7.0078125 C-1.10289307 6.09660645 -1.10812988 5.18540039 -1.11352539 4.24658203 C-1 2 -1 2 0 0 Z " fill="#4A898E" transform="translate(126,234)"/>
    <path d="M0 0 C1.15224028 0.92961422 2.29659176 1.8690125 3.4375 2.8125 C4.07558594 3.33457031 4.71367188 3.85664063 5.37109375 4.39453125 C7 6 7 6 8 9 C6.71372289 10.17260611 5.42080217 11.3379283 4.125 12.5 C3.40570313 13.1496875 2.68640625 13.799375 1.9453125 14.46875 C0 16 0 16 -2 16 C-2 15.01 -2 14.02 -2 13 C-0.08512183 11.34043892 1.73576718 10.13211641 4 9 C3.67 7.35 3.34 5.7 3 4 C2.34 4 1.68 4 1 4 C0.34 4.66 -0.32 5.32 -1 6 C-0.67 4.02 -0.34 2.04 0 0 Z " fill="#3B7B7E" transform="translate(115,376)"/>
    <path d="M0 0 C0.99 0.66 1.98 1.32 3 2 C2.690625 2.763125 2.38125 3.52625 2.0625 4.3125 C1.32106407 6.18789677 0.63771783 8.08684652 0 10 C-1.65 10 -3.3 10 -5 10 C-5.66 8.68 -6.32 7.36 -7 6 C-6.34 6 -5.68 6 -5 6 C-4.67 5.01 -4.34 4.02 -4 3 C-1.9375 1.3125 -1.9375 1.3125 0 0 Z " fill="#C74F69" transform="translate(214,165)"/>
    <path d="M0 0 C0.99 0.33 1.98 0.66 3 1 C4.32152306 6.88678452 3.8741103 9.86766018 1 15 C0.01 15.33 -0.98 15.66 -2 16 C-1.85833643 14.08302063 -1.71150416 12.16642277 -1.5625 10.25 C-1.48128906 9.18265625 -1.40007812 8.1153125 -1.31640625 7.015625 C-1.05909227 4.56320038 -0.68187782 2.36078709 0 0 Z " fill="#307878" transform="translate(376,249)"/>
    <path d="M0 0 C0.33 0 0.66 0 1 0 C1.33 1.98 1.66 3.96 2 6 C1.34 6 0.68 6 0 6 C0 17.88 0 29.76 0 42 C-0.33 42 -0.66 42 -1 42 C-1.33 28.8 -1.66 15.6 -2 2 C-1.34 2 -0.68 2 0 2 C0 1.34 0 0.68 0 0 Z " fill="#CB4F6B" transform="translate(220,180)"/>
    <path d="M0 0 C0.66 0.33 1.32 0.66 2 1 C0.68 4.3 -0.64 7.6 -2 11 C-2.99 11 -3.98 11 -5 11 C-5 10.34 -5 9.68 -5 9 C-6.65 9.66 -8.3 10.32 -10 11 C-6.79239749 7.19364503 -3.59253891 3.45436433 0 0 Z " fill="#2B7476" transform="translate(374,117)"/>
    <path d="M0 0 C0.33 0.99 0.66 1.98 1 3 C0.55269531 3.43570313 0.10539062 3.87140625 -0.35546875 4.3203125 C-4.14521536 8.05349573 -7.66459583 11.856013 -11 16 C-12.04449911 12.86650268 -11.93423645 12.01031744 -11 9 C-9.68 9 -8.36 9 -7 9 C-6.67 7.68 -6.34 6.36 -6 5 C-5.01 5 -4.02 5 -3 5 C-2.87625 4.360625 -2.7525 3.72125 -2.625 3.0625 C-2.41875 2.381875 -2.2125 1.70125 -2 1 C-1.34 0.67 -0.68 0.34 0 0 Z " fill="#336B6F" transform="translate(365,115)"/>
    <path d="M0 0 C4.06372019 2.0318601 5.39757528 7.93751479 7 12 C7.8125 15.6875 7.8125 15.6875 8 18 C6.68 17.67 5.36 17.34 4 17 C3.87625 15.4840625 3.87625 15.4840625 3.75 13.9375 C3.34447177 10.908327 2.47918357 8.84435205 0.875 6.25 C0.25625 5.1775 -0.3625 4.105 -1 3 C-0.67 2.01 -0.34 1.02 0 0 Z " fill="#367F7E" transform="translate(144,331)"/>
    <path d="M0 0 C-3.88476443 2.58984296 -7.39151186 2.99533003 -11.9375 3.75 C-13.15469727 3.97623047 -13.15469727 3.97623047 -14.39648438 4.20703125 C-15.18216797 4.33980469 -15.96785156 4.47257812 -16.77734375 4.609375 C-17.49140381 4.73280273 -18.20546387 4.85623047 -18.94116211 4.98339844 C-21.44433868 5.00358295 -22.86757008 4.25828659 -25 3 C-16.54704214 0.92686581 -8.71703754 -0.48032656 0 0 Z " fill="#2F6664" transform="translate(228,78)"/>
    <path d="M0 0 C2.31625061 1.11777771 4.62784605 2.24374092 6.9375 3.375 C7.59685547 3.69210937 8.25621094 4.00921875 8.93554688 4.3359375 C13.88671875 6.7734375 13.88671875 6.7734375 15 9 C13.68 9.33 12.36 9.66 11 10 C11 9.34 11 8.68 11 8 C10.0925 7.87625 9.185 7.7525 8.25 7.625 C3.24415584 6.66233766 3.24415584 6.66233766 1 5 C0.61140671 3.34534471 0.27327991 1.67755983 0 0 Z " fill="#377C7A" transform="translate(192,392)"/>
    <path d="M0 0 C1.11378736 3.31822704 0.87728255 5.24820779 -0.21484375 8.546875 C-0.48876953 9.39121094 -0.76269531 10.23554687 -1.04492188 11.10546875 C-1.48674805 12.41451172 -1.48674805 12.41451172 -1.9375 13.75 C-2.22818359 14.63816406 -2.51886719 15.52632812 -2.81835938 16.44140625 C-3.53639318 18.63082079 -4.26385523 20.81662189 -5 23 C-5.33 23 -5.66 23 -6 23 C-6 19.7 -6 16.4 -6 13 C-5.34 13 -4.68 13 -4 13 C-3.896875 12.154375 -3.79375 11.30875 -3.6875 10.4375 C-2.9171146 6.58557299 -1.63725486 3.55924969 0 0 Z " fill="#347275" transform="translate(421,99)"/>
    <path d="M0 0 C1.15242188 0.10183594 2.30484375 0.20367187 3.4921875 0.30859375 C4.36101562 0.39238281 5.22984375 0.47617188 6.125 0.5625 C6.125 0.8925 6.125 1.2225 6.125 1.5625 C-1.20150983 2.62237432 -8.52471227 3.68046547 -15.875 4.5625 C-14.75 2.625 -14.75 2.625 -12.875 0.5625 C-8.4564853 -0.51643964 -4.50711447 -0.41391868 0 0 Z " fill="#256B6E" transform="translate(235.875,90.4375)"/>
    <path d="M0 0 C0.99 0 1.98 0 3 0 C3.33 0.99 3.66 1.98 4 3 C2.34765625 5.91796875 2.34765625 5.91796875 0.0625 9.1875 C-0.32357422 9.74630859 -0.70964844 10.30511719 -1.10742188 10.88085938 C-2.06242753 12.26031198 -3.03004093 13.63102056 -4 15 C-4.62109375 13.15234375 -4.62109375 13.15234375 -5 11 C-3.67189785 9.32916181 -2.33827304 7.66270287 -1 6 C-0.74875174 3.33238236 -0.74875174 3.33238236 -1 1 C-0.67 0.67 -0.34 0.34 0 0 Z " fill="#337477" transform="translate(404,337)"/>
    <path d="M0 0 C0.33 0.99 0.66 1.98 1 3 C-1.54782974 5.97246802 -4.45340883 7.41535288 -8 9 C-8.66 9 -9.32 9 -10 9 C-10 8.01 -10 7.02 -10 6 C-9.01 6 -8.02 6 -7 6 C-7 5.01 -7 4.02 -7 3 C-4.69 2.01 -2.38 1.02 0 0 Z " fill="#B9536F" transform="translate(239,161)"/>
    <path d="M0 0 C0.33 0.66 0.66 1.32 1 2 C2.32 2 3.64 2 5 2 C5 1.34 5 0.68 5 0 C5.66 0 6.32 0 7 0 C6.50769231 5.53846154 6.50769231 5.53846154 3.9375 7.875 C2.9784375 8.431875 2.9784375 8.431875 2 9 C2 8.01 2 7.02 2 6 C1.34 6 0.68 6 0 6 C-0.38218767 4.34385343 -0.71395102 2.67542976 -1 1 C-0.67 0.67 -0.34 0.34 0 0 Z " fill="#B8435D" transform="translate(209,232)"/>
    <path d="M0 0 C2.5 1.375 2.5 1.375 5 3 C5 3.66 5 4.32 5 5 C5.639375 5.2475 6.27875 5.495 6.9375 5.75 C9 7 9 7 9.75 9.125 C9.87375 10.053125 9.87375 10.053125 10 11 C7.0625 10.1875 7.0625 10.1875 4 9 C3.67 8.01 3.34 7.02 3 6 C2.01 5.67 1.02 5.34 0 5 C0 3.35 0 1.7 0 0 Z " fill="#388280" transform="translate(176,379)"/>
    <path d="M0 0 C1.47968324 0.14049518 2.95874516 0.28754826 4.4375 0.4375 C5.67306641 0.55931641 5.67306641 0.55931641 6.93359375 0.68359375 C9 1 9 1 10 2 C10.07271741 4.35313555 10.08370868 6.70833668 10.0625 9.0625 C10.05347656 10.35285156 10.04445313 11.64320313 10.03515625 12.97265625 C10.02355469 13.97167969 10.01195312 14.97070312 10 16 C9.67 16 9.34 16 9 16 C8.67 11.38 8.34 6.76 8 2 C3.13867505 2.49152482 3.13867505 2.49152482 1 5 C0.67 3.35 0.34 1.7 0 0 Z " fill="#903144" transform="translate(318,241)"/>
    <path d="M0 0 C0.33 1.32 0.66 2.64 1 4 C1.54140625 3.83628906 2.0828125 3.67257812 2.640625 3.50390625 C5.40222593 2.91409413 7.92818139 2.90390692 10.75 2.9375 C11.73484375 2.94652344 12.7196875 2.95554687 13.734375 2.96484375 C14.85585938 2.98224609 14.85585938 2.98224609 16 3 C10.69672257 5.74307453 6.90269168 6.19040941 1 6 C0 5 0 5 -0.0625 2.4375 C-0.041875 1.633125 -0.02125 0.82875 0 0 Z " fill="#8E3051" transform="translate(261,336)"/>
    <path d="M0 0 C0.66 0 1.32 0 2 0 C2.48452463 6.94485303 0.69287491 13.2990368 -1 20 C-1.33 20 -1.66 20 -2 20 C-2.22731216 13.06697907 -1.42002108 6.7831364 0 0 Z " fill="#286669" transform="translate(78,209)"/>
    <path d="M0 0 C1 2 1 2 0.9375 3.8125 C-0.50315446 7.17402707 -3.15503151 8.79369791 -6 11 C-6.66 11.66 -7.32 12.32 -8 13 C-7.48118887 8.8275894 -6.28848726 6.74583179 -3.4375 3.6875 C-2.79683594 2.99011719 -2.15617187 2.29273438 -1.49609375 1.57421875 C-1.00238281 1.05472656 -0.50867188 0.53523437 0 0 Z " fill="#307C7D" transform="translate(120,128)"/>
    <path d="M0 0 C0.99 0.495 0.99 0.495 2 1 C2 1.66 2 2.32 2 3 C2.66 3 3.32 3 4 3 C5.33583899 7.84241633 6.25328119 11.93437617 6 17 C3.38832897 14.38832897 2.92868518 12.0278941 1.875 8.5 C1.52179688 7.3346875 1.16859375 6.169375 0.8046875 4.96875 C0 2 0 2 0 0 Z " fill="#2F696C" transform="translate(83,303)"/>
    <path d="M0 0 C0.66 0 1.32 0 2 0 C1.49396008 2.16874252 1.00016187 3.99967627 0 6 C0.66 6.33 1.32 6.66 2 7 C0.02 7.66 -1.96 8.32 -4 9 C-4.66 8.34 -5.32 7.68 -6 7 C-5.04882866 5.8274869 -4.08882991 4.66212985 -3.125 3.5 C-2.59132813 2.8503125 -2.05765625 2.200625 -1.5078125 1.53125 C-0.76144531 0.77328125 -0.76144531 0.77328125 0 0 Z " fill="#BB3E5A" transform="translate(291,256)"/>
    <path d="M0 0 C0.66 0.33 1.32 0.66 2 1 C2 1.99 2 2.98 2 4 C0.34467377 5.01866229 -1.32260138 6.01810813 -3 7 C-3.33 7.99 -3.66 8.98 -4 10 C-4.99 9.67 -5.98 9.34 -7 9 C-5.64978174 4.58110387 -3.73884332 2.65654657 0 0 Z " fill="#318488" transform="translate(136,113)"/>
    <path d="M0 0 C1.32 0.66 2.64 1.32 4 2 C4 2.99 4 3.98 4 5 C3.34 5 2.68 5 2 5 C1.67 6.98 1.34 8.96 1 11 C0.01 10.67 -0.98 10.34 -2 10 C-1.34 6.7 -0.68 3.4 0 0 Z " fill="#24797C" transform="translate(90,347)"/>
    <path d="M0 0 C0.33 0 0.66 0 1 0 C0.59683075 6.31631828 -1.71232131 10.64993984 -5 16 C-5.66 16 -6.32 16 -7 16 C-5.125 10.25 -5.125 10.25 -4 8 C-3.34 8 -2.68 8 -2 8 C-2 6.68 -2 5.36 -2 4 C-1.34 4 -0.68 4 0 4 C0 2.68 0 1.36 0 0 Z " fill="#407478" transform="translate(415,320)"/>
    <path d="M0 0 C0.66 0 1.32 0 2 0 C2.33951619 1.56116819 2.67132544 3.12401338 3 4.6875 C3.185625 5.55761719 3.37125 6.42773438 3.5625 7.32421875 C3.98972047 9.93712964 4.07616201 12.35813038 4 15 C3.34 15 2.68 15 2 15 C0.53329887 9.77487721 -0.22577402 5.4185766 0 0 Z " fill="#276D72" transform="translate(78,279)"/>
    <path d="M0 0 C-4.61496078 4.03809068 -9.92852877 6 -16 6 C-16 5.34 -16 4.68 -16 4 C-14.274773 3.32320342 -12.54461061 2.65898 -10.8125 2 C-9.84957031 1.62875 -8.88664062 1.2575 -7.89453125 0.875 C-5.07817642 0.02363228 -2.91835561 -0.17072647 0 0 Z " fill="#397476" transform="translate(329,408)"/>
    <path d="M0 0 C0.99 0.33 1.98 0.66 3 1 C1.73548068 2.31858428 0.46314525 3.62967631 -0.8125 4.9375 C-1.52019531 5.66839844 -2.22789062 6.39929688 -2.95703125 7.15234375 C-5 9 -5 9 -8 10 C-7.67 8.35 -7.34 6.7 -7 5 C-5.824375 4.87625 -5.824375 4.87625 -4.625 4.75 C-1.85250896 4.27193661 -1.85250896 4.27193661 -0.6875 1.9375 C-0.460625 1.298125 -0.23375 0.65875 0 0 Z " fill="#287274" transform="translate(377,374)"/>
    <path d="M0 0 C3.27286682 2.7819368 4.96427697 6.27954067 7 10 C6.01 10 5.02 10 4 10 C3.34 8.35 2.68 6.7 2 5 C1.01 5.33 0.02 5.66 -1 6 C-1.625 4.125 -1.625 4.125 -2 2 C-1.34 1.34 -0.68 0.68 0 0 Z " fill="#418889" transform="translate(92,343)"/>
    <path d="M0 0 C0.66 0 1.32 0 2 0 C1.94004678 1.79260132 1.8512759 3.58425795 1.75 5.375 C1.70359375 6.37273437 1.6571875 7.37046875 1.609375 8.3984375 C1 11 1 11 -1.046875 12.3515625 C-1.69140625 12.56554687 -2.3359375 12.77953125 -3 13 C-2.68817931 11.58318366 -2.37551263 10.16655349 -2.0625 8.75 C-1.88847656 7.96109375 -1.71445312 7.1721875 -1.53515625 6.359375 C-1.052444 4.23121297 -0.53836822 2.11467158 0 0 Z " fill="#3A7675" transform="translate(371,272)"/>
    <path d="M0 0 C0.66 0 1.32 0 2 0 C2.33 1.65 2.66 3.3 3 5 C2.34 5 1.68 5 1 5 C0.34 6.65 -0.32 8.3 -1 10 C-2.42944945 7.64561267 -3.08661261 6.51967567 -2.625 3.75 C-2.41875 3.1725 -2.2125 2.595 -2 2 C-1.34 2 -0.68 2 0 2 C0 1.34 0 0.68 0 0 Z " fill="#2F7E7E" transform="translate(84,192)"/>
    <path d="M0 0 C0.33 0.66 0.66 1.32 1 2 C-1.25 4.5625 -1.25 4.5625 -4 7 C-6.3125 6.8125 -6.3125 6.8125 -8 6 C-7.67 5.01 -7.34 4.02 -7 3 C-3.4375 1.3125 -3.4375 1.3125 0 0 Z " fill="#347C7D" transform="translate(296,398)"/>
    <path d="M0 0 C0.99 0.33 1.98 0.66 3 1 C2.40072174 4.35595828 1.15377564 6.37801226 -1 9 C-1 8.01 -1 7.02 -1 6 C-1.66 6 -2.32 6 -3 6 C-1.125 1.125 -1.125 1.125 0 0 Z " fill="#BA4162" transform="translate(285,173)"/>
    <path d="M0 0 C2.31 0 4.62 0 7 0 C7.66 1.32 8.32 2.64 9 4 C5.7 4 2.4 4 -1 4 C-0.34 3.67 0.32 3.34 1 3 C0.67 2.01 0.34 1.02 0 0 Z " fill="#CF4160" transform="translate(223,163)"/>
    <path d="M0 0 C1.65 0 3.3 0 5 0 C1.56638098 3.63559661 -1.7973607 6.36113346 -6 9 C-6.66 8.67 -7.32 8.34 -8 8 C-7.67 7.34 -7.34 6.68 -7 6 C-6.01 6 -5.02 6 -4 6 C-4 5.34 -4 4.68 -4 4 C-3.01 3.67 -2.02 3.34 -1 3 C-0.67 2.01 -0.34 1.02 0 0 Z " fill="#346C6C" transform="translate(357,389)"/>
    <path d="M0 0 C0.66 0.33 1.32 0.66 2 1 C2.59355614 3.64860427 2.74209269 6.29197322 3 9 C1.35 9.33 -0.3 9.66 -2 10 C-1.34 6.7 -0.68 3.4 0 0 Z " fill="#287274" transform="translate(82,377)"/>
    <path d="M0 0 C0.99 0.66 1.98 1.32 3 2 C0.34345343 5.73884332 -1.58110387 7.64978174 -6 9 C-5.67 7.68 -5.34 6.36 -5 5 C-3.35 4.67 -1.7 4.34 0 4 C0 2.68 0 1.36 0 0 Z " fill="#AA435A" transform="translate(211,327)"/>
    <path d="M0 0 C0.66 0 1.32 0 2 0 C2.33 0.99 2.66 1.98 3 3 C2.01 4.485 2.01 4.485 1 6 C0.34 6 -0.32 6 -1 6 C-0.67 6.99 -0.34 7.98 0 9 C-0.99 9.99 -1.98 10.98 -3 12 C-4 10 -4 10 -3.22265625 7.6171875 C-2.83980469 6.71226562 -2.45695312 5.80734375 -2.0625 4.875 C-1.68222656 3.96492188 -1.30195313 3.05484375 -0.91015625 2.1171875 C-0.60980469 1.41851562 -0.30945313 0.71984375 0 0 Z " fill="#2C7C7C" transform="translate(356,323)"/>
    <path d="M0 0 C0.99 0 1.98 0 3 0 C3.33 3.96 3.66 7.92 4 12 C3.01 12.495 3.01 12.495 2 13 C1.34 8.71 0.68 4.42 0 0 Z " fill="#399394" transform="translate(422,276)"/>
    <path d="M0 0 C4.38889325 0.66498383 7.9811485 2.18757677 12 4 C12 4.33 12 4.66 12 5 C7.82851735 5.19402245 4.71055417 5.01430083 1 3 C0.67 2.01 0.34 1.02 0 0 Z " fill="#32777A" transform="translate(316,87)"/>
    <path d="M0 0 C0.33 0 0.66 0 1 0 C1 2.97 1 5.94 1 9 C-0.32 9.33 -1.64 9.66 -3 10 C-4.28436734 11.9837767 -4.28436734 11.9837767 -5 14 C-5.66 13.67 -6.32 13.34 -7 13 C-6.57847656 12.39671875 -6.15695312 11.7934375 -5.72265625 11.171875 C-5.17480469 10.37265625 -4.62695312 9.5734375 -4.0625 8.75 C-3.51722656 7.96109375 -2.97195313 7.1721875 -2.41015625 6.359375 C-1.10377559 4.17363007 -0.45019489 2.4846199 0 0 Z " fill="#A14257" transform="translate(278,317)"/>
    <path d="M0 0 C0.66 0 1.32 0 2 0 C2.66 2.97 3.32 5.94 4 9 C3.34 9 2.68 9 2 9 C2 9.66 2 10.32 2 11 C1.34 11 0.68 11 0 11 C0 7.37 0 3.74 0 0 Z " fill="#3C8280" transform="translate(131,292)"/>
    <path d="M0 0 C1.32 0.99 2.64 1.98 4 3 C1.93243723 5.7972908 0.85559629 6.02692273 -2.6875 6.6875 C-3.780625 6.790625 -4.87375 6.89375 -6 7 C-4.625 5.5 -4.625 5.5 -3 4 C-2.34 4 -1.68 4 -1 4 C-1.33 3.01 -1.66 2.02 -2 1 C-1.34 0.67 -0.68 0.34 0 0 Z " fill="#AF3759" transform="translate(302,247)"/>
    <path d="M0 0 C0.99 0.33 1.98 0.66 3 1 C1.85793642 2.50287218 0.71125043 4.00223284 -0.4375 5.5 C-1.39462891 6.75296875 -1.39462891 6.75296875 -2.37109375 8.03125 C-2.90863281 8.6809375 -3.44617188 9.330625 -4 10 C-4.33 10 -4.66 10 -5 10 C-5 8.35 -5 6.7 -5 5 C-4.34 4.67 -3.68 4.34 -3 4 C-2.67 3.01 -2.34 2.02 -2 1 C-1.34 0.67 -0.68 0.34 0 0 Z " fill="#346E70" transform="translate(162,133)"/>
    <path d="M0 0 C0.66 0.99 1.32 1.98 2 3 C1.01 3.33 0.02 3.66 -1 4 C-1 4.66 -1 5.32 -1 6 C-2.98 6.33 -4.96 6.66 -7 7 C-5.54744301 3.17053157 -3.57104439 1.87949705 0 0 Z " fill="#367272" transform="translate(150,103)"/>
    <path d="M0 0 C6.75479274 -0.7342166 11.18524285 1.72408048 17 5 C15 6 15 6 11.5234375 5.00390625 C10.13802064 4.53099953 8.75528453 4.05018356 7.375 3.5625 C6.31539063 3.19801758 6.31539063 3.19801758 5.234375 2.82617188 C3.48726713 2.22412795 1.74332214 1.61291983 0 1 C0 0.67 0 0.34 0 0 Z " fill="#2B6466" transform="translate(274,93)"/>
    <path d="M0 0 C3.62685461 -0.20149192 5.91228852 0.04444939 9 2 C10.75 3.625 10.75 3.625 12 5 C7.6822542 4.49202991 3.96697886 3.80317221 0 2 C0 1.34 0 0.68 0 0 Z " fill="#347071" transform="translate(181,411)"/>
    <path d="M0 0 C1.65 0.33 3.3 0.66 5 1 C2.53698577 3.46301423 0.72639964 3.76766521 -2.625 4.625 C-3.62789062 4.88539062 -4.63078125 5.14578125 -5.6640625 5.4140625 C-6.43492187 5.60742188 -7.20578125 5.80078125 -8 6 C-8 5.34 -8 4.68 -8 4 C-5.09399739 2.74335022 -3.20395416 2 0 2 C0 1.34 0 0.68 0 0 Z " fill="#296E75" transform="translate(214,94)"/>
    <path d="M0 0 C3.28788824 3.11016455 5.83735988 6.01618925 8 10 C5.83125748 9.49396008 4.00032373 9.00016187 2 8 C1.3671875 6.15234375 1.3671875 6.15234375 0.875 3.9375 C0.70742187 3.20402344 0.53984375 2.47054688 0.3671875 1.71484375 C0.24601563 1.14894531 0.12484375 0.58304687 0 0 Z " fill="#3E7879" transform="translate(160,360)"/>
    <path d="M0 0 C0.66 0 1.32 0 2 0 C1.71640113 1.4391134 1.42278601 2.87625518 1.125 4.3125 C0.88136719 5.51326172 0.88136719 5.51326172 0.6328125 6.73828125 C0 9 0 9 -2 12 C-3.01911045 8.10885099 -3.16782969 6.39403493 -1.5625 2.625 C-1.046875 1.75875 -0.53125 0.8925 0 0 Z " fill="#387273" transform="translate(136,181)"/>
    <path d="M0 0 C3.63 0 7.26 0 11 0 C11 0.33 11 0.66 11 1 C5.77487721 2.46670113 1.4185766 3.22577402 -4 3 C-2.68 2.67 -1.36 2.34 0 2 C0 1.34 0 0.68 0 0 Z " fill="#246166" transform="translate(292,417)"/>
    <path d="M0 0 C0.66 0.99 1.32 1.98 2 3 C1.34 4.32 0.68 5.64 0 7 C-0.66 7 -1.32 7 -2 7 C-2 8.32 -2 9.64 -2 11 C-2.99 11.33 -3.98 11.66 -5 12 C-3.68938409 7.76570244 -2.05643741 3.92592596 0 0 Z " fill="#3F7476" transform="translate(350,338)"/>
    <path d="M0 0 C1.32 0 2.64 0 4 0 C4 0.99 4 1.98 4 3 C3.34 3 2.68 3 2 3 C1.34 4.32 0.68 5.64 0 7 C-0.66 6.67 -1.32 6.34 -2 6 C-1.49396008 3.83125748 -1.00016187 2.00032373 0 0 Z " fill="#367F80" transform="translate(345,155)"/>
    <path d="M0 0 C1.98 0.99 1.98 0.99 4 2 C4 2.66 4 3.32 4 4 C4.66 4 5.32 4 6 4 C5.67 5.65 5.34 7.3 5 9 C0.57142857 3.85714286 0.57142857 3.85714286 0 0 Z " fill="#2C6D6D" transform="translate(100,343)"/>
    <path d="M0 0 C0.33 0 0.66 0 1 0 C1 1.98 1 3.96 1 6 C0.34 6 -0.32 6 -1 6 C-1.33 7.32 -1.66 8.64 -2 10 C-2.66 10 -3.32 10 -4 10 C-3.52449439 8.51965236 -3.04448784 7.04074979 -2.5625 5.5625 C-2.29566406 4.73878906 -2.02882813 3.91507813 -1.75390625 3.06640625 C-1 1 -1 1 0 0 Z " fill="#2D7978" transform="translate(92,169)"/>
    <path d="M0 0 C1.32 0.33 2.64 0.66 4 1 C1.19195732 3.96404505 -1.18315774 6.44499019 -5 8 C-5.33 7.34 -5.66 6.68 -6 6 C-5.01 6 -4.02 6 -3 6 C-2.690625 5.38125 -2.38125 4.7625 -2.0625 4.125 C-1.375 2.75 -0.6875 1.375 0 0 Z " fill="#347272" transform="translate(185,109)"/>
    <path d="M0 0 C-0.33 2.31 -0.66 4.62 -1 7 C-1.33 6.34 -1.66 5.68 -2 5 C-4.01669827 3.86649466 -4.01669827 3.86649466 -6 3 C-2.25 0 -2.25 0 0 0 Z " fill="#3A7377" transform="translate(429,75)"/>
    <path d="M0 0 C0.33 0.99 0.66 1.98 1 3 C1.66 3.33 2.32 3.66 3 4 C1.02 6.31 -0.96 8.62 -3 11 C-3.33 10.01 -3.66 9.02 -4 8 C-3.34 8 -2.68 8 -2 8 C-1.7834375 7.13375 -1.7834375 7.13375 -1.5625 6.25 C-1.04166667 4.16666667 -0.52083333 2.08333333 0 0 Z " fill="#3B6E6F" transform="translate(396,350)"/>
    <path d="M0 0 C0.66 0 1.32 0 2 0 C1.67 3.3 1.34 6.6 1 10 C0.34 10 -0.32 10 -1 10 C-1.02689216 8.52093108 -1.04634621 7.04172517 -1.0625 5.5625 C-1.07410156 4.73878906 -1.08570312 3.91507813 -1.09765625 3.06640625 C-1 1 -1 1 0 0 Z " fill="#477B7E" transform="translate(127,224)"/>
    <path d="M0 0 C0.66 0.33 1.32 0.66 2 1 C0.35 2.98 -1.3 4.96 -3 7 C-4.32 6.67 -5.64 6.34 -7 6 C-4.69 4.02 -2.38 2.04 0 0 Z " fill="#317677" transform="translate(410,88)"/>
    <path d="M0 0 C4.455 0.99 4.455 0.99 9 2 C9 2.66 9 3.32 9 4 C3.375 4.25 3.375 4.25 0 2 C0 1.34 0 0.68 0 0 Z " fill="#2F7C7C" transform="translate(303,83)"/>
    <path d="M0 0 C2 3 2 3 1.75 6.0625 C1 9 1 9 -1 11 C-2.41687119 6.74938644 -1.19320039 4.28148375 0 0 Z " fill="#2C6870" transform="translate(85,366)"/>
    <path d="M0 0 C0.99 0.66 1.98 1.32 3 2 C1.824375 2.680625 1.824375 2.680625 0.625 3.375 C-2.03698031 4.88428742 -2.03698031 4.88428742 -4 7 C-4.33 5.35 -4.66 3.7 -5 2 C-2.6875 0.875 -2.6875 0.875 0 0 Z " fill="#BD586E" transform="translate(266,333)"/>
    <path d="M0 0 C0.33 0 0.66 0 1 0 C1.390625 5.859375 1.390625 5.859375 1 8 C-0.32 9.32 -1.64 10.64 -3 12 C-2.01 8.04 -1.02 4.08 0 0 Z " fill="#367373" transform="translate(360,310)"/>
    <path d="M0 0 C2.475 0.495 2.475 0.495 5 1 C5 1.66 5 2.32 5 3 C5.66 3 6.32 3 7 3 C6.67 4.32 6.34 5.64 6 7 C4.02 4.69 2.04 2.38 0 0 Z " fill="#3F7C7F" transform="translate(362,234)"/>
    <path d="M0 0 C3.7952131 1.49097658 5.61042731 3.75700849 8 7 C5 7 5 7 2.3125 4.625 C0 2 0 2 0 0 Z " fill="#3D9FA0" transform="translate(353,224)"/>
    <path d="M0 0 C0.33 1.32 0.66 2.64 1 4 C0.01 4.495 0.01 4.495 -1 5 C-1.65213292 7.02463255 -1.65213292 7.02463255 -2 9 C-2.99 9 -3.98 9 -5 9 C-3.7508258 5.54074837 -2.32472205 2.85306797 0 0 Z " fill="#A53C5E" transform="translate(207,172)"/>
    <path d="M0 0 C0.66 0.33 1.32 0.66 2 1 C1.37240135 3.92879371 0.41377341 6.36095629 -1 9 C-1.33 9 -1.66 9 -2 9 C-2.25 3.375 -2.25 3.375 0 0 Z " fill="#32797C" transform="translate(144,161)"/>
    <path d="M0 0 C1.32 0 2.64 0 4 0 C3.60196439 2.88575819 3.20990931 3.82189513 0.9375 5.75 C0.298125 6.1625 -0.34125 6.575 -1 7 C-0.67 4.69 -0.34 2.38 0 0 Z " fill="#337A7E" transform="translate(354,124)"/>
    <path d="M0 0 C0.66 0 1.32 0 2 0 C2 0.99 2 1.98 2 3 C2.66 3.33 3.32 3.66 4 4 C2.1875 6 2.1875 6 0 8 C-0.99 8 -1.98 8 -3 8 C-3 7.34 -3 6.68 -3 6 C-2.01 6 -1.02 6 0 6 C0 4.02 0 2.04 0 0 Z " fill="#2F777B" transform="translate(199,97)"/>
    <path d="M0 0 C0.90105469 0.19464844 1.80210937 0.38929687 2.73046875 0.58984375 C3.41753906 0.74582031 4.10460937 0.90179688 4.8125 1.0625 C4.8125 1.3925 4.8125 1.7225 4.8125 2.0625 C3.02116022 2.11641993 1.22936101 2.15525571 -0.5625 2.1875 C-1.56023437 2.21070312 -2.55796875 2.23390625 -3.5859375 2.2578125 C-6.1875 2.0625 -6.1875 2.0625 -8.1875 0.0625 C-4.80475734 -1.06508089 -3.41971777 -0.75500262 0 0 Z " fill="#2C7777" transform="translate(297.1875,80.9375)"/>
    <path d="M0 0 C-1.41199578 0.39222105 -2.82992764 0.76311059 -4.25 1.125 C-5.03890625 1.33382812 -5.8278125 1.54265625 -6.640625 1.7578125 C-9.4203071 2.04314411 -10.65887549 1.44977894 -13 0 C-11.39950296 -0.39036513 -9.79437629 -0.76178619 -8.1875 -1.125 C-7.29417969 -1.33382812 -6.40085938 -1.54265625 -5.48046875 -1.7578125 C-3 -2 -3 -2 0 0 Z " fill="#356E70" transform="translate(216,81)"/>
    <path d="M0 0 C0.33 1.32 0.66 2.64 1 4 C-1.375 5.5625 -1.375 5.5625 -4 7 C-4.66 6.67 -5.32 6.34 -6 6 C-4.02 4.02 -2.04 2.04 0 0 Z " fill="#2C6D6E" transform="translate(329,372)"/>
    <path d="M0 0 C0.33 0 0.66 0 1 0 C1.19491452 1.45736087 1.38069358 2.91594593 1.5625 4.375 C1.66691406 5.18710937 1.77132813 5.99921875 1.87890625 6.8359375 C2 9 2 9 1 11 C0.34 11 -0.32 11 -1 11 C-1.125 3.375 -1.125 3.375 0 0 Z " fill="#318284" transform="translate(422,293)"/>
    <path d="M0 0 C1.32 0.33 2.64 0.66 4 1 C3.25 5.75 3.25 5.75 1 8 C0.67 5.36 0.34 2.72 0 0 Z " fill="#29807F" transform="translate(146,152)"/>
    <path d="M0 0 C2.3125 0.125 2.3125 0.125 5 1 C6.8125 4.0625 6.8125 4.0625 8 7 C4.2047869 5.50902342 2.38957269 3.24299151 0 0 Z " fill="#2E5A5C" transform="translate(122,374)"/>
    <path d="M0 0 C2.62187494 1.04874998 3.79371361 1.64931313 5.25 4.125 C5.4975 4.74375 5.745 5.3625 6 6 C5.01 6.33 4.02 6.66 3 7 C0 2.25 0 2.25 0 0 Z " fill="#3C7D7F" transform="translate(410,245)"/>
    <path d="M0 0 C0.66 0 1.32 0 2 0 C2 3.3 2 6.6 2 10 C-1 6 -1 6 -0.6875 2.75 C-0.460625 1.8425 -0.23375 0.935 0 0 Z " fill="#47898B" transform="translate(387,208)"/>
    <path d="M0 0 C0.66 1.32 1.32 2.64 2 4 C1.01 5.485 1.01 5.485 0 7 C-0.99 7 -1.98 7 -3 7 C-1.125 1.125 -1.125 1.125 0 0 Z " fill="#2B7273" transform="translate(100,154)"/>
    <path d="M0 0 C0.33 1.32 0.66 2.64 1 4 C0.34 4 -0.32 4 -1 4 C-1 5.65 -1 7.3 -1 9 C-1.99 9 -2.98 9 -4 9 C-2.94124926 5.59687262 -1.99097846 2.98646769 0 0 Z " fill="#397273" transform="translate(351,144)"/>
    <path d="M0 0 C0.625 1.8125 0.625 1.8125 1 4 C0.01 5.485 0.01 5.485 -1 7 C-2.32 6.67 -3.64 6.34 -5 6 C-3.35 4.02 -1.7 2.04 0 0 Z " fill="#3C898D" transform="translate(111,138)"/>
    <path d="M0 0 C2.31 0 4.62 0 7 0 C7.33 1.65 7.66 3.3 8 5 C4.62496331 3.8141763 2.35277269 2.72426311 0 0 Z " fill="#327174" transform="translate(302,104)"/>
    <path d="M0 0 C0.99 0 1.98 0 3 0 C3 0.66 3 1.32 3 2 C3.66 2.33 4.32 2.66 5 3 C4.34 3 3.68 3 3 3 C2.67 4.65 2.34 6.3 2 8 C1.67 8 1.34 8 1 8 C0.67 5.36 0.34 2.72 0 0 Z " fill="#378089" transform="translate(418,96)"/>
    <path d="M0 0 C0.99 0.66 1.98 1.32 3 2 C-3.625 2.25 -3.625 2.25 -7 0 C-4.28569464 -1.35715268 -2.88661718 -0.77445827 0 0 Z " fill="#296265" transform="translate(208,418)"/>
    <path d="M0 0 C0 0.66 0 1.32 0 2 C-1.62626404 3.38232443 -3.2925002 4.71937515 -5 6 C-5.66 5.67 -6.32 5.34 -7 5 C-5.64703851 0.94111554 -4.27479763 0 0 0 Z " fill="#3C676E" transform="translate(82,416)"/>
    <path d="M0 0 C2.97 0.33 5.94 0.66 9 1 C9 1.99 9 2.98 9 4 C6.03 3.01 3.06 2.02 0 1 C0 0.67 0 0.34 0 0 Z " fill="#336169" transform="translate(171,408)"/>
    <path d="M0 0 C0.99 0.66 1.98 1.32 3 2 C1.02 3.32 -0.96 4.64 -3 6 C-3.33 4.68 -3.66 3.36 -4 2 C-2.68 2 -1.36 2 0 2 C0 1.34 0 0.68 0 0 Z " fill="#3F7A7B" transform="translate(94,404)"/>
    <path d="M0 0 C1.32 0 2.64 0 4 0 C4 0.66 4 1.32 4 2 C4.66 2 5.32 2 6 2 C6.66 3.32 7.32 4.64 8 6 C4.08597051 4.73058503 2.20927127 3.48832306 0 0 Z " fill="#2E7377" transform="translate(141,390)"/>
    <path d="M0 0 C2.97 0.495 2.97 0.495 6 1 C6.33 2.65 6.66 4.3 7 6 C1.125 2.25 1.125 2.25 0 0 Z " fill="#2D7178" transform="translate(133,384)"/>
    <path d="M0 0 C2.475 0.495 2.475 0.495 5 1 C5 2.32 5 3.64 5 5 C2.525 4.01 2.525 4.01 0 3 C0 2.01 0 1.02 0 0 Z " fill="#49868A" transform="translate(129,379)"/>
    <path d="M0 0 C0 2.31 0 4.62 0 7 C-0.66 7 -1.32 7 -2 7 C-2.66 5.02 -3.32 3.04 -4 1 C-2 0 -2 0 0 0 Z " fill="#2A7275" transform="translate(93,320)"/>
    <path d="M0 0 C2 1 4 2 6 3 C6.625 5.0625 6.625 5.0625 7 7 C3.17053157 5.54744301 1.87949705 3.57104439 0 0 Z " fill="#347278" transform="translate(312,109)"/>
    <path d="M0 0 C1.98 0.66 3.96 1.32 6 2 C6 2.99 6 3.98 6 5 C4.02 4.34 2.04 3.68 0 3 C0 2.01 0 1.02 0 0 Z " fill="#317777" transform="translate(340,98)"/>
    <path d="M0 0 C0.66 0.33 1.32 0.66 2 1 C-3.75 6 -3.75 6 -6 6 C-6 5.01 -6 4.02 -6 3 C-4.03533587 1.93224776 -2.03144641 0.93446535 0 0 Z " fill="#336869" transform="translate(119,386)"/>
    <path d="M0 0 C0.33 0.66 0.66 1.32 1 2 C-0.98 4.97 -0.98 4.97 -3 8 C-3.99 7.67 -4.98 7.34 -6 7 C-4.02 4.69 -2.04 2.38 0 0 Z " fill="#356C6E" transform="translate(336,363)"/>
    <path d="M0 0 C4.875 4.75 4.875 4.75 6 7 C4.125 6.6875 4.125 6.6875 2 6 C0 3 0 3 0 0 Z " fill="#357B7D" transform="translate(106,365)"/>
    <path d="M0 0 C0.99 0.33 1.98 0.66 3 1 C1.02 3.97 1.02 3.97 -1 7 C-1.625 5.1875 -1.625 5.1875 -2 3 C-1.34 2.01 -0.68 1.02 0 0 Z " fill="#357A78" transform="translate(340,357)"/>
    <path d="M0 0 C0.66 0 1.32 0 2 0 C1.67 1.32 1.34 2.64 1 4 C-0.65 4.33 -2.3 4.66 -4 5 C-2.71937515 3.2925002 -1.38232443 1.62626404 0 0 Z " fill="#B6357C" transform="translate(291,256)"/>
    <path d="M0 0 C3 3 3 3 3.1875 5.6875 C3.0946875 6.8321875 3.0946875 6.8321875 3 8 C2.01 8.33 1.02 8.66 0 9 C0 6.03 0 3.06 0 0 Z " fill="#5C9C9C" transform="translate(125,249)"/>
    <path d="M0 0 C0.99 0.33 1.98 0.66 3 1 C0.03 3.475 0.03 3.475 -3 6 C-2.625 3.5625 -2.625 3.5625 -2 1 C-1.34 0.67 -0.68 0.34 0 0 Z " fill="#35696B" transform="translate(176,118)"/>
    <path d="M0 0 C0.66 0.33 1.32 0.66 2 1 C2 1.99 2 2.98 2 4 C0.02 4.99 0.02 4.99 -2 6 C-2 2 -2 2 0 0 Z " fill="#2F7676" transform="translate(136,113)"/>
    <path d="M0 0 C0.66 0 1.32 0 2 0 C3.46726798 3.81489674 2.43336419 6.29011622 1 10 C0.67 10 0.34 10 0 10 C0 6.7 0 3.4 0 0 Z " fill="#39787B" transform="translate(415,112)"/>
    <path d="M0 0 C0.66 0.99 1.32 1.98 2 3 C1.0409375 3.433125 1.0409375 3.433125 0.0625 3.875 C-2.15206079 4.82823451 -2.15206079 4.82823451 -3 7 C-3.66 6.67 -4.32 6.34 -5 6 C-5 5.34 -5 4.68 -5 4 C-3.37373596 2.61767557 -1.7074998 1.28062485 0 0 Z " fill="#3B908E" transform="translate(142,108)"/>
    </svg>

);

type OpenDevSocietyBrandingProps = {
    text?: string; // e.g. "Designed by"
    name?: string; // e.g. "Open Dev Society"
    style?: React.CSSProperties;
    className?: string;
    logoSize?: number;
    textColor?: string;
    outerStyle?: React.CSSProperties;      // NEW: outer style for container
    outerClassName?: string;
};

export const OpenDevSocietyBranding: React.FC<OpenDevSocietyBrandingProps> = ({
                                                                                  text = "Initiative by",
                                                                                  name = "Open Dev Society",
                                                                                  style = {},
                                                                                  className = "border-2 border-gray-300 px-3 py-0.5 rounded-lg",
                                                                                  logoSize = 40,
                                                                                  textColor = "#fff",
                                                                                  outerStyle = {},
                                                                                  outerClassName = "",
                                                                              }) => (
    <div
        className={outerClassName}
        style={{
            ...outerStyle,
            // No default padding or margin here—control everything outside!
        }}>
        <div
            className={className}
            style={{
                textAlign: "center",
                display: "inline-flex",
                alignItems: "center",
                gap: "0.2em",
                background: "none",
                fontFamily: "Inter, 'SF Pro Display', Arial, sans-serif",
                fontWeight: 500,
                fontSize: "1.17em",
                color: textColor,
                ...style,
            }}
        >
            <span className="text-sm -tracking-tighter" style={{ opacity: 0.75 }}>{text}</span>
            <ODSLogoSVG size={logoSize} />
            <span className="tracking-tight" style={{fontWeight: "bold" }}>{name}</span>
        </div>
    </div>
);

export default OpenDevSocietyBranding;

================================================
FILE: components/SearchCommand.tsx
================================================
"use client"

import { useEffect, useState } from "react"
import { CommandDialog, CommandEmpty, CommandInput, CommandList } from "@/components/ui/command"
import {Button} from "@/components/ui/button";
import {Loader2,  TrendingUp} from "lucide-react";
import Link from "next/link";
import {searchStocks} from "@/lib/actions/finnhub.actions";
import {useDebounce} from "@/hooks/useDebounce";

export default function SearchCommand({ renderAs = 'button', label = 'Add stock', initialStocks }: SearchCommandProps) {
    const [open, setOpen] = useState(false)
    const [searchTerm, setSearchTerm] = useState("")
    const [loading, setLoading] = useState(false)
    const [stocks, setStocks] = useState<StockWithWatchlistStatus[]>(initialStocks);

    const isSearchMode = !!searchTerm.trim();
    const displayStocks = isSearchMode ? stocks : stocks?.slice(0, 10);

    useEffect(() => {
        const onKeyDown = (e: KeyboardEvent) => {
            if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") {
                e.preventDefault()
                setOpen(v => !v)
            }
        }
        window.addEventListener("keydown", onKeyDown)
        return () => window.removeEventListener("keydown", onKeyDown)
    }, [])

    const handleSearch = async () => {
        if(!isSearchMode) return setStocks(initialStocks);

        setLoading(true)
        try {
            const results = await searchStocks(searchTerm.trim());
            setStocks(results);
        } catch {
            setStocks([])
        } finally {
            setLoading(false)
        }
    }

    const debouncedSearch = useDebounce(handleSearch, 300);

    useEffect(() => {
        debouncedSearch();
    }, [searchTerm]);

    const handleSelectStock = () => {
        setOpen(false);
        setSearchTerm("");
        setStocks(initialStocks);
    }

    return (
        <>
            {renderAs === 'text' ? (
                <button
                    type="button"
                    onClick={() => setOpen(true)}
                    className="search-text"
                >
                    {label}
                </button>
            ): (
                <Button onClick={() => setOpen(true)} className="search-btn">
                    {label}
                </Button>
            )}
            <CommandDialog open={open} onOpenChange={setOpen} className="search-dialog">
                <div className="search-field">
                    <CommandInput value={searchTerm} onValueChange={setSearchTerm} placeholder="Search stocks..." className="search-input" />
                    {loading && <Loader2 className="search-loader" />}
                </div>
                <CommandList className="search-list">
                    {loading ? (
                        <CommandEmpty className="search-list-empty">Loading stocks...</CommandEmpty>
                    ) : displayStocks?.length === 0 ? (
                        <div className="search-list-indicator">
                            {isSearchMode ? 'No results found' : 'No stocks available'}
                        </div>
                    ) : (
                        <ul>
                            <div className="search-count">
                                {isSearchMode ? 'Search results' : 'Popular stocks'}
                                {` `}({displayStocks?.length || 0})
                            </div>
                            {displayStocks?.map((stock, i) => (
                                <li key={stock.symbol} className="search-item">
                                    <Link
                                        href={`/stocks/${stock.symbol}`}
                                        onClick={handleSelectStock}
                                        className="search-item-link"
                                    >
                                        <TrendingUp className="h-4 w-4 text-gray-500" />
                                        <div  className="flex-1">
                                            <div className="search-item-name">
                                                {stock.name}
                                            </div>
                                            <div className="text-sm text-gray-500">
                                                {stock.symbol} | {stock.exchange } | {stock.type}
                                            </div>
                                        </div>

                                    </Link>
                                </li>
                            ))}
                        </ul>
                    )
                    }
                </CommandList>
            </CommandDialog>
        </>
    )
}

================================================
FILE: components/SirayBanner.tsx
================================================

"use client";

import { useState } from "react";
import { X } from "lucide-react";
import Link from "next/link";

export d
Download .txt
gitextract_k4znbyx3/

├── .github/
│   └── FUNDING.yml
├── .gitignore
├── .idea/
│   ├── .gitignore
│   ├── OpenStock.iml
│   ├── git_toolbox_prj.xml
│   ├── inspectionProfiles/
│   │   └── Project_Default.xml
│   ├── modules.xml
│   └── vcs.xml
├── API_DOCS.md
├── Dockerfile
├── LICENSE
├── README.md
├── app/
│   ├── (auth)/
│   │   ├── layout.tsx
│   │   ├── sign-in/
│   │   │   └── page.tsx
│   │   └── sign-up/
│   │       └── page.tsx
│   ├── (root)/
│   │   ├── about/
│   │   │   └── page.tsx
│   │   ├── api-docs/
│   │   │   └── page.tsx
│   │   ├── help/
│   │   │   └── page.tsx
│   │   ├── layout.tsx
│   │   ├── page.tsx
│   │   ├── stocks/
│   │   │   └── [symbol]/
│   │   │       └── page.tsx
│   │   ├── terms/
│   │   │   └── page.tsx
│   │   └── watchlist/
│   │       └── page.tsx
│   ├── api/
│   │   └── inngest/
│   │       └── route.ts
│   ├── globals.css
│   └── layout.tsx
├── components/
│   ├── DonatePopup.tsx
│   ├── Footer.tsx
│   ├── Header.tsx
│   ├── NavItems.tsx
│   ├── OpenDevSocietyBranding.tsx
│   ├── SearchCommand.tsx
│   ├── SirayBanner.tsx
│   ├── TradingViewWidget.tsx
│   ├── UserDropdown.tsx
│   ├── WatchlistButton.tsx
│   ├── forms/
│   │   ├── CountrySelectField.tsx
│   │   ├── FooterLink.tsx
│   │   ├── InputField.tsx
│   │   └── SelectField.tsx
│   ├── ui/
│   │   ├── avatar.tsx
│   │   ├── button.tsx
│   │   ├── command.tsx
│   │   ├── dialog.tsx
│   │   ├── dropdown-menu.tsx
│   │   ├── input.tsx
│   │   ├── label.tsx
│   │   ├── popover.tsx
│   │   ├── select.tsx
│   │   └── sonner.tsx
│   └── watchlist/
│       ├── AlertsPanel.tsx
│       ├── CreateAlertModal.tsx
│       ├── NewsGrid.tsx
│       ├── TradingViewWatchlist.tsx
│       ├── WatchlistManager.tsx
│       ├── WatchlistStockChip.tsx
│       └── WatchlistTable.tsx
├── components.json
├── database/
│   ├── models/
│   │   ├── alert.model.ts
│   │   └── watchlist.model.ts
│   └── mongoose.ts
├── docker-compose.yml
├── eslint.config.mjs
├── hooks/
│   ├── useDebounce.ts
│   └── useTradingViewWidget.tsx
├── lib/
│   ├── actions/
│   │   ├── alert.actions.ts
│   │   ├── auth.actions.ts
│   │   ├── finnhub.actions.ts
│   │   ├── user.actions.ts
│   │   └── watchlist.actions.ts
│   ├── better-auth/
│   │   └── auth.ts
│   ├── constants.ts
│   ├── inngest/
│   │   ├── client.ts
│   │   ├── functions.ts
│   │   └── prompts.ts
│   ├── kit.ts
│   ├── nodemailer/
│   │   ├── index.ts
│   │   └── templates.ts
│   └── utils.ts
├── middleware/
│   └── index.ts
├── next.config.ts
├── package.json
├── postcss.config.mjs
├── scripts/
│   ├── check-env.mjs
│   ├── check_db_name.js
│   ├── create-kit-tag.mjs
│   ├── inspect-user.mjs
│   ├── list-kit-forms.mjs
│   ├── migrate-users-to-kit.mjs
│   ├── resolve_srv.js
│   ├── seed-inactive-user.mjs
│   ├── test-db.mjs
│   ├── test-db.ts
│   ├── test-kit.mjs
│   └── verify-watchlist.mjs
├── tsconfig.json
└── types/
    └── global.d.ts
Download .txt
SYMBOL INDEX (200 symbols across 57 files)

FILE: app/(root)/about/page.tsx
  function AboutPage (line 21) | function AboutPage() {
  function FeatureCard (line 106) | function FeatureCard({ icon, title, desc, color }: any) {
  function SocialButton (line 122) | function SocialButton({ href, icon, label }: any) {

FILE: app/(root)/api-docs/page.tsx
  function ApiDocsPage (line 24) | function ApiDocsPage() {
  function Badge (line 204) | function Badge({ children, color }: { children: React.ReactNode, color: ...
  function JobCard (line 217) | function JobCard({ icon, title, trigger, desc, color }: any) {
  function StackItem (line 235) | function StackItem({ title, desc, url }: any) {

FILE: app/(root)/help/page.tsx
  function HelpPage (line 17) | function HelpPage() {
  function HelpCard (line 113) | function HelpCard({ icon, title, desc, link, linkText }: any) {

FILE: app/(root)/stocks/[symbol]/page.tsx
  function StockDetails (line 17) | async function StockDetails({ params }: StockDetailsPageProps) {

FILE: app/(root)/terms/page.tsx
  function TermsPage (line 9) | function TermsPage() {
  function PromiseItem (line 89) | function PromiseItem({ text }: { text: string }) {

FILE: app/(root)/watchlist/page.tsx
  function WatchlistPage (line 14) | async function WatchlistPage() {

FILE: app/layout.tsx
  function RootLayout (line 22) | function RootLayout({

FILE: components/DonatePopup.tsx
  constant DONATE_POPUP_KEY (line 14) | const DONATE_POPUP_KEY = 'opendevsociety-donate-popup-dismissed';
  constant DONATE_POPUP_DELAY (line 15) | const DONATE_POPUP_DELAY = 3000;
  constant DONATE_POPUP_COOLDOWN (line 16) | const DONATE_POPUP_COOLDOWN = 24 * 60 * 60 * 1000;
  constant GITHUB_SPONSOR_URL (line 18) | const GITHUB_SPONSOR_URL = 'https://github.com/sponsors/ravixalgorithm';
  function DonatePopup (line 20) | function DonatePopup() {

FILE: components/OpenDevSocietyBranding.tsx
  type OpenDevSocietyBrandingProps (line 145) | type OpenDevSocietyBrandingProps = {

FILE: components/SearchCommand.tsx
  function SearchCommand (line 11) | function SearchCommand({ renderAs = 'button', label = 'Add stock', initi...

FILE: components/SirayBanner.tsx
  function SirayBanner (line 8) | function SirayBanner() {

FILE: components/TradingViewWidget.tsx
  type TradingViewWidgetProps (line 9) | interface TradingViewWidgetProps {

FILE: components/WatchlistButton.tsx
  type WatchlistButtonProps (line 6) | interface WatchlistButtonProps {

FILE: components/forms/CountrySelectField.tsx
  type CountrySelectProps (line 25) | type CountrySelectProps = {

FILE: components/ui/avatar.tsx
  function Avatar (line 8) | function Avatar({
  function AvatarImage (line 24) | function AvatarImage({
  function AvatarFallback (line 37) | function AvatarFallback({

FILE: components/ui/button.tsx
  function Button (line 37) | function Button({

FILE: components/ui/command.tsx
  function Command (line 16) | function Command({
  function CommandDialog (line 32) | function CommandDialog({
  function CommandInput (line 63) | function CommandInput({
  function CommandList (line 85) | function CommandList({
  function CommandEmpty (line 101) | function CommandEmpty({
  function CommandGroup (line 113) | function CommandGroup({
  function CommandSeparator (line 129) | function CommandSeparator({
  function CommandItem (line 142) | function CommandItem({
  function CommandShortcut (line 158) | function CommandShortcut({

FILE: components/ui/dialog.tsx
  function Dialog (line 9) | function Dialog({
  function DialogTrigger (line 15) | function DialogTrigger({
  function DialogPortal (line 21) | function DialogPortal({
  function DialogClose (line 27) | function DialogClose({
  function DialogOverlay (line 33) | function DialogOverlay({
  function DialogContent (line 49) | function DialogContent({
  function DialogHeader (line 83) | function DialogHeader({ className, ...props }: React.ComponentProps<"div...
  function DialogFooter (line 93) | function DialogFooter({ className, ...props }: React.ComponentProps<"div...
  function DialogTitle (line 106) | function DialogTitle({
  function DialogDescription (line 119) | function DialogDescription({

FILE: components/ui/dropdown-menu.tsx
  function DropdownMenu (line 9) | function DropdownMenu({
  function DropdownMenuPortal (line 15) | function DropdownMenuPortal({
  function DropdownMenuTrigger (line 23) | function DropdownMenuTrigger({
  function DropdownMenuContent (line 34) | function DropdownMenuContent({
  function DropdownMenuGroup (line 54) | function DropdownMenuGroup({
  function DropdownMenuItem (line 62) | function DropdownMenuItem({
  function DropdownMenuCheckboxItem (line 85) | function DropdownMenuCheckboxItem({
  function DropdownMenuRadioGroup (line 111) | function DropdownMenuRadioGroup({
  function DropdownMenuRadioItem (line 122) | function DropdownMenuRadioItem({
  function DropdownMenuLabel (line 146) | function DropdownMenuLabel({
  function DropdownMenuSeparator (line 166) | function DropdownMenuSeparator({
  function DropdownMenuShortcut (line 179) | function DropdownMenuShortcut({
  function DropdownMenuSub (line 195) | function DropdownMenuSub({
  function DropdownMenuSubTrigger (line 201) | function DropdownMenuSubTrigger({
  function DropdownMenuSubContent (line 225) | function DropdownMenuSubContent({

FILE: components/ui/input.tsx
  function Input (line 5) | function Input({ className, type, ...props }: React.ComponentProps<"inpu...

FILE: components/ui/label.tsx
  function Label (line 8) | function Label({

FILE: components/ui/popover.tsx
  function Popover (line 8) | function Popover({
  function PopoverTrigger (line 14) | function PopoverTrigger({
  function PopoverContent (line 20) | function PopoverContent({
  function PopoverAnchor (line 42) | function PopoverAnchor({

FILE: components/ui/select.tsx
  function Select (line 9) | function Select({
  function SelectGroup (line 15) | function SelectGroup({
  function SelectValue (line 21) | function SelectValue({
  function SelectTrigger (line 27) | function SelectTrigger({
  function SelectContent (line 53) | function SelectContent({
  function SelectLabel (line 88) | function SelectLabel({
  function SelectItem (line 101) | function SelectItem({
  function SelectSeparator (line 125) | function SelectSeparator({
  function SelectScrollUpButton (line 138) | function SelectScrollUpButton({
  function SelectScrollDownButton (line 156) | function SelectScrollDownButton({

FILE: components/watchlist/AlertsPanel.tsx
  type AlertsPanelProps (line 8) | interface AlertsPanelProps {
  function AlertsPanel (line 13) | function AlertsPanel({ alerts, onRefresh }: AlertsPanelProps) {

FILE: components/watchlist/CreateAlertModal.tsx
  type CreateAlertModalProps (line 12) | interface CreateAlertModalProps {
  function CreateAlertModal (line 24) | function CreateAlertModal({

FILE: components/watchlist/NewsGrid.tsx
  type NewsGridProps (line 8) | interface NewsGridProps {
  function NewsGrid (line 12) | function NewsGrid({ news }: NewsGridProps) {

FILE: components/watchlist/TradingViewWatchlist.tsx
  type TradingViewWatchlistProps (line 6) | interface TradingViewWatchlistProps {
  function TradingViewWatchlist (line 10) | function TradingViewWatchlist({ symbols }: TradingViewWatchlistProps) {

FILE: components/watchlist/WatchlistManager.tsx
  type WatchlistManagerProps (line 10) | interface WatchlistManagerProps {
  function WatchlistManager (line 15) | function WatchlistManager({ initialItems, userId }: WatchlistManagerProp...

FILE: components/watchlist/WatchlistStockChip.tsx
  type WatchlistStockChipProps (line 9) | interface WatchlistStockChipProps {
  function WatchlistStockChip (line 14) | function WatchlistStockChip({ symbol, userId }: WatchlistStockChipProps) {

FILE: components/watchlist/WatchlistTable.tsx
  type WatchlistTableProps (line 12) | interface WatchlistTableProps {
  function WatchlistTable (line 18) | function WatchlistTable({ data, userId, onRefresh }: WatchlistTableProps) {

FILE: database/models/alert.model.ts
  type IAlert (line 3) | interface IAlert extends Document {

FILE: database/models/watchlist.model.ts
  type WatchlistItem (line 3) | interface WatchlistItem extends Document {

FILE: database/mongoose.ts
  constant MONGODB_URI (line 3) | const MONGODB_URI = process.env.MONGODB_URI;

FILE: hooks/useDebounce.ts
  function useDebounce (line 5) | function useDebounce(callback: () => void, delay: number) {

FILE: lib/actions/alert.actions.ts
  function createAlert (line 8) | async function createAlert(params: {
  function getUserAlerts (line 30) | async function getUserAlerts(userId: string) {
  function deleteAlert (line 42) | async function deleteAlert(alertId: string) {
  function toggleAlert (line 55) | async function toggleAlert(alertId: string, active: boolean) {

FILE: lib/actions/finnhub.actions.ts
  constant FINNHUB_BASE_URL (line 7) | const FINNHUB_BASE_URL = 'https://finnhub.io/api/v1';
  constant NEXT_PUBLIC_FINNHUB_API_KEY (line 8) | const NEXT_PUBLIC_FINNHUB_API_KEY = process.env.NEXT_PUBLIC_FINNHUB_API_...
  function fetchJSON (line 10) | async function fetchJSON<T>(url: string, revalidateSeconds?: number): Pr...
  function getQuote (line 25) | async function getQuote(symbol: string) {
  function getCompanyProfile (line 37) | async function getCompanyProfile(symbol: string) {
  function getWatchlistData (line 49) | async function getWatchlistData(symbols: string[]) {
  function getNews (line 76) | async function getNews(symbols?: string[]): Promise<MarketNewsArticle[]> {

FILE: lib/actions/watchlist.actions.ts
  function addToWatchlist (line 9) | async function addToWatchlist(userId: string, symbol: string, company: s...
  function removeFromWatchlist (line 33) | async function removeFromWatchlist(userId: string, symbol: string) {
  function getUserWatchlist (line 46) | async function getUserWatchlist(userId: string) {
  function isStockInWatchlist (line 58) | async function isStockInWatchlist(userId: string, symbol: string) {
  function getWatchlistSymbolsByEmail (line 71) | async function getWatchlistSymbolsByEmail(email: string): Promise<string...

FILE: lib/constants.ts
  constant NAV_ITEMS (line 1) | const NAV_ITEMS = [
  constant INVESTMENT_GOALS (line 9) | const INVESTMENT_GOALS = [
  constant RISK_TOLERANCE_OPTIONS (line 16) | const RISK_TOLERANCE_OPTIONS = [
  constant PREFERRED_INDUSTRIES (line 22) | const PREFERRED_INDUSTRIES = [
  constant ALERT_TYPE_OPTIONS (line 30) | const ALERT_TYPE_OPTIONS = [
  constant CONDITION_OPTIONS (line 35) | const CONDITION_OPTIONS = [
  constant MARKET_OVERVIEW_WIDGET_CONFIG (line 41) | const MARKET_OVERVIEW_WIDGET_CONFIG = {
  constant HEATMAP_WIDGET_CONFIG (line 99) | const HEATMAP_WIDGET_CONFIG = {
  constant TOP_STORIES_WIDGET_CONFIG (line 118) | const TOP_STORIES_WIDGET_CONFIG = {
  constant MARKET_DATA_WIDGET_CONFIG (line 129) | const MARKET_DATA_WIDGET_CONFIG = {
  constant POPULAR_STOCK_SYMBOLS (line 266) | const POPULAR_STOCK_SYMBOLS = [
  constant NO_MARKET_NEWS (line 328) | const NO_MARKET_NEWS =
  constant WATCHLIST_TABLE_HEADER (line 331) | const WATCHLIST_TABLE_HEADER = [

FILE: lib/inngest/functions.ts
  type TriggeredAlert (line 322) | type TriggeredAlert = { alert: any; currentPrice: number };

FILE: lib/inngest/prompts.ts
  constant PERSONALIZED_WELCOME_EMAIL_PROMPT (line 1) | const PERSONALIZED_WELCOME_EMAIL_PROMPT = `Generate highly personalized ...
  constant NEWS_SUMMARY_EMAIL_PROMPT (line 50) | const NEWS_SUMMARY_EMAIL_PROMPT = `Generate HTML content for a market ne...
  constant TRADINGVIEW_SYMBOL_MAPPING_PROMPT (line 201) | const TRADINGVIEW_SYMBOL_MAPPING_PROMPT = `You are an expert in financia...

FILE: lib/kit.ts
  constant KIT_API_URL (line 1) | const KIT_API_URL = 'https://api.kit.com/v4';
  type KitConfig (line 3) | interface KitConfig {

FILE: lib/nodemailer/templates.ts
  constant WELCOME_EMAIL_TEMPLATE (line 1) | const WELCOME_EMAIL_TEMPLATE = `<!DOCTYPE html>
  constant NEWS_SUMMARY_EMAIL_TEMPLATE (line 168) | const NEWS_SUMMARY_EMAIL_TEMPLATE = `<!DOCTYPE html>
  constant STOCK_ALERT_UPPER_EMAIL_TEMPLATE (line 307) | const STOCK_ALERT_UPPER_EMAIL_TEMPLATE = `<!DOCTYPE html>
  constant STOCK_ALERT_LOWER_EMAIL_TEMPLATE (line 515) | const STOCK_ALERT_LOWER_EMAIL_TEMPLATE = `<!DOCTYPE html>
  constant VOLUME_ALERT_EMAIL_TEMPLATE (line 724) | const VOLUME_ALERT_EMAIL_TEMPLATE = `<!DOCTYPE html>
  constant INACTIVE_USER_REMINDER_EMAIL_TEMPLATE (line 944) | const INACTIVE_USER_REMINDER_EMAIL_TEMPLATE = `<!DOCTYPE html>

FILE: lib/utils.ts
  function cn (line 4) | function cn(...inputs: ClassValue[]) {
  function delay (line 24) | function delay(ms: number) {
  function formatMarketCapValue (line 29) | function formatMarketCapValue(marketCapUsd: number): string {
  function formatNumber (line 122) | function formatNumber(num: number): string {
  function formatSymbolForTradingView (line 157) | function formatSymbolForTradingView(symbol: string): string {

FILE: middleware/index.ts
  function middleware (line 4) | async function middleware(request: NextRequest) {

FILE: scripts/check-env.mjs
  function maskValue (line 106) | function maskValue(value) {

FILE: scripts/check_db_name.js
  function checkDBs (line 11) | async function checkDBs() {

FILE: scripts/create-kit-tag.mjs
  constant KIT_API_KEY (line 5) | const KIT_API_KEY = process.env.KIT_API_KEY;
  function createTag (line 7) | async function createTag() {

FILE: scripts/inspect-user.mjs
  function checkSchema (line 6) | async function checkSchema() {

FILE: scripts/list-kit-forms.mjs
  constant KIT_API_KEY (line 5) | const KIT_API_KEY = process.env.KIT_API_KEY;
  function listForms (line 7) | async function listForms() {

FILE: scripts/migrate-users-to-kit.mjs
  constant MONGODB_URI (line 12) | const MONGODB_URI = process.env.MONGODB_URI;
  constant KIT_API_KEY (line 13) | const KIT_API_KEY = process.env.KIT_API_KEY;
  constant KIT_WELCOME_FORM_ID (line 14) | const KIT_WELCOME_FORM_ID = process.env.KIT_WELCOME_FORM_ID;
  function addSubscriberToKit (line 22) | async function addSubscriberToKit(email, firstName) {
  function runMigration (line 63) | async function runMigration() {

FILE: scripts/resolve_srv.js
  constant SRV_ADDR (line 11) | const SRV_ADDR = '_mongodb._tcp.cluster0.scwvh5g.mongodb.net';
  function getStandardConnectionString (line 13) | async function getStandardConnectionString() {

FILE: scripts/seed-inactive-user.mjs
  function run (line 17) | async function run() {

FILE: scripts/test-db.mjs
  function main (line 17) | async function main() {

FILE: scripts/test-db.ts
  function main (line 3) | async function main() {

FILE: scripts/test-kit.mjs
  constant KIT_API_KEY (line 5) | const KIT_API_KEY = process.env.KIT_API_KEY;
  constant KIT_API_SECRET (line 6) | const KIT_API_SECRET = process.env.KIT_API_SECRET;
  function runTest (line 16) | async function runTest() {

FILE: scripts/verify-watchlist.mjs
  constant MOCK_USER_ID (line 8) | const MOCK_USER_ID = 'verify-user-' + Date.now();
  constant SYMBOL (line 9) | const SYMBOL = 'AAPL';
  constant COMPANY (line 10) | const COMPANY = 'Apple Inc';
  function verifyFinnhub (line 29) | async function verifyFinnhub() {
  function verifyDB (line 40) | async function verifyDB() {
  function main (line 48) | async function main() {

FILE: types/global.d.ts
  type SignInFormData (line 2) | type SignInFormData = {
  type SignUpFormData (line 7) | type SignUpFormData = {
  type CountrySelectProps (line 17) | type CountrySelectProps = {
  type FormInputProps (line 25) | type FormInputProps = {
  type Option (line 37) | type Option = {
  type SelectFieldProps (line 42) | type SelectFieldProps = {
  type FooterLinkProps (line 52) | type FooterLinkProps = {
  type SearchCommandProps (line 58) | type SearchCommandProps = {
  type WelcomeEmailData (line 64) | type WelcomeEmailData = {
  type User (line 70) | type User = {
  type Stock (line 76) | type Stock = {
  type StockWithWatchlistStatus (line 83) | type StockWithWatchlistStatus = Stock & {
  type FinnhubSearchResult (line 87) | type FinnhubSearchResult = {
  type FinnhubSearchResponse (line 94) | type FinnhubSearchResponse = {
  type StockDetailsPageProps (line 99) | type StockDetailsPageProps = {
  type WatchlistButtonProps (line 105) | type WatchlistButtonProps = {
  type QuoteData (line 114) | type QuoteData = {
  type ProfileData (line 119) | type ProfileData = {
  type FinancialsData (line 124) | type FinancialsData = {
  type SelectedStock (line 128) | type SelectedStock = {
  type WatchlistTableProps (line 134) | type WatchlistTableProps = {
  type StockWithData (line 138) | type StockWithData = {
  type AlertsListProps (line 151) | type AlertsListProps = {
  type MarketNewsArticle (line 155) | type MarketNewsArticle = {
  type WatchlistNewsProps (line 167) | type WatchlistNewsProps = {
  type SearchCommandProps (line 171) | type SearchCommandProps = {
  type AlertData (line 180) | type AlertData = {
  type AlertModalProps (line 188) | type AlertModalProps = {
  type RawNewsArticle (line 196) | type RawNewsArticle = {
  type Alert (line 208) | type Alert = {
Condensed preview — 97 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (473K chars).
[
  {
    "path": ".github/FUNDING.yml",
    "chars": 826,
    "preview": "# These are supported funding model platforms\n\ngithub: [ravixalgorithm]\npatreon: # Replace with a single Patreon usernam"
  },
  {
    "path": ".gitignore",
    "chars": 480,
    "preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pn"
  },
  {
    "path": ".idea/.gitignore",
    "chars": 176,
    "preview": "# Default ignored files\n/shelf/\n/workspace.xml\n# Editor-based HTTP Client requests\n/httpRequests/\n# Datasource local sto"
  },
  {
    "path": ".idea/OpenStock.iml",
    "chars": 458,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<module type=\"WEB_MODULE\" version=\"4\">\n  <component name=\"NewModuleRootManager\">\n"
  },
  {
    "path": ".idea/git_toolbox_prj.xml",
    "chars": 480,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"GitToolBoxProjectSettings\">\n    <option "
  },
  {
    "path": ".idea/inspectionProfiles/Project_Default.xml",
    "chars": 251,
    "preview": "<component name=\"InspectionProjectProfileManager\">\n  <profile version=\"1.0\">\n    <option name=\"myName\" value=\"Project De"
  },
  {
    "path": ".idea/modules.xml",
    "chars": 270,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"ProjectModuleManager\">\n    <modules>\n   "
  },
  {
    "path": ".idea/vcs.xml",
    "chars": 180,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"VcsDirectoryMappings\">\n    <mapping dire"
  },
  {
    "path": "API_DOCS.md",
    "chars": 3586,
    "preview": "<div align=\"center\">\n  <img src=\"public/assets/images/logo.png\" alt=\"OpenStock Logo\" width=\"120\" />\n  <h1>OpenStock API "
  },
  {
    "path": "Dockerfile",
    "chars": 698,
    "preview": "# Use official Node.js 20 Alpine image as base\nFROM node:20-alpine\n\n# Set working directory\nWORKDIR /app\n\n# Copy package"
  },
  {
    "path": "LICENSE",
    "chars": 34523,
    "preview": "                    GNU AFFERO GENERAL PUBLIC LICENSE\n                       Version 3, 19 November 2007\n\n Copyright (C)"
  },
  {
    "path": "README.md",
    "chars": 16660,
    "preview": "<div align=\"center\">\n  Checkout new amazing projects also, <a href=\"github.com/open-dev-society/openreadme\" target=\"_bla"
  },
  {
    "path": "app/(auth)/layout.tsx",
    "chars": 2274,
    "preview": "import Link from \"next/link\";\nimport React from \"react\";\nimport Image from \"next/image\";\nimport {headers} from \"next/hea"
  },
  {
    "path": "app/(auth)/sign-in/page.tsx",
    "chars": 3480,
    "preview": "'use client';\n\nimport { useForm } from 'react-hook-form';\nimport { Button } from '@/components/ui/button';\nimport InputF"
  },
  {
    "path": "app/(auth)/sign-up/page.tsx",
    "chars": 5564,
    "preview": "'use client';\n\nimport { useForm } from \"react-hook-form\";\nimport { Button } from \"@/components/ui/button\";\nimport InputF"
  },
  {
    "path": "app/(root)/about/page.tsx",
    "chars": 6345,
    "preview": "\nimport React from 'react';\nimport Image from 'next/image';\nimport Link from 'next/link';\nimport {\n    Users,\n    Globe,"
  },
  {
    "path": "app/(root)/api-docs/page.tsx",
    "chars": 10356,
    "preview": "\nimport React from 'react';\nimport Image from 'next/image';\nimport Link from 'next/link';\nimport {\n  Server,\n  Cpu,\n  Sh"
  },
  {
    "path": "app/(root)/help/page.tsx",
    "chars": 5038,
    "preview": "import { Metadata } from 'next';\nimport {\n  HelpCircle,\n  MessageCircle,\n  BookOpen,\n  Lightbulb,\n  Mail,\n  Github,\n  Ch"
  },
  {
    "path": "app/(root)/layout.tsx",
    "chars": 960,
    "preview": "import Header from \"@/components/Header\";\nimport { auth } from \"@/lib/better-auth/auth\";\nimport { headers } from \"next/h"
  },
  {
    "path": "app/(root)/page.tsx",
    "chars": 2638,
    "preview": "import TradingViewWidget from \"@/components/TradingViewWidget\";\nimport {\n    HEATMAP_WIDGET_CONFIG,\n    MARKET_DATA_WIDG"
  },
  {
    "path": "app/(root)/stocks/[symbol]/page.tsx",
    "chars": 3484,
    "preview": "import TradingViewWidget from \"@/components/TradingViewWidget\";\nimport WatchlistButton from \"@/components/WatchlistButto"
  },
  {
    "path": "app/(root)/terms/page.tsx",
    "chars": 4695,
    "preview": "import { Metadata } from 'next';\nimport { Shield, FileText, Check, AlertTriangle, Scale } from 'lucide-react';\n\nexport c"
  },
  {
    "path": "app/(root)/watchlist/page.tsx",
    "chars": 2903,
    "preview": "import React, { Suspense } from 'react';\nimport { auth } from '@/lib/better-auth/auth';\nimport { headers } from 'next/he"
  },
  {
    "path": "app/api/inngest/route.ts",
    "chars": 367,
    "preview": "import { serve } from \"inngest/next\";\nimport { inngest } from \"@/lib/inngest/client\";\nimport { sendWeeklyNewsSummary, se"
  },
  {
    "path": "app/globals.css",
    "chars": 16325,
    "preview": "@import \"tailwindcss\";\n@import \"tw-animate-css\";\n\n@custom-variant dark (&:is(.dark *));\n\n@theme inline {\n    --color-bac"
  },
  {
    "path": "app/layout.tsx",
    "chars": 1150,
    "preview": "import type { Metadata } from \"next\";\nimport { Geist, Geist_Mono } from \"next/font/google\";\nimport { Analytics } from \"@"
  },
  {
    "path": "components/DonatePopup.tsx",
    "chars": 4259,
    "preview": "'use client';\n\nimport React, { useEffect, useState } from 'react';\nimport {\n    Dialog,\n    DialogContent,\n    DialogDes"
  },
  {
    "path": "components/Footer.tsx",
    "chars": 6673,
    "preview": "import Link from \"next/link\";\nimport Image from \"next/image\";\nimport OpenDevSocietyBranding from \"./OpenDevSocietyBrandi"
  },
  {
    "path": "components/Header.tsx",
    "chars": 1042,
    "preview": "import Link from \"next/link\";\nimport Image from \"next/image\";\nimport NavItems from \"@/components/NavItems\";\nimport UserD"
  },
  {
    "path": "components/NavItems.tsx",
    "chars": 2463,
    "preview": "'use client'\n\n\nimport React, { createContext, useContext } from 'react'\nimport {NAV_ITEMS} from \"@/lib/constants\";\nimpor"
  },
  {
    "path": "components/OpenDevSocietyBranding.tsx",
    "chars": 52298,
    "preview": "import React from \"react\";\n\n// SVG version of your logo (image 2)\n// Replace with real SVG for sharpest results; this is"
  },
  {
    "path": "components/SearchCommand.tsx",
    "chars": 4730,
    "preview": "\"use client\"\n\nimport { useEffect, useState } from \"react\"\nimport { CommandDialog, CommandEmpty, CommandInput, CommandLis"
  },
  {
    "path": "components/SirayBanner.tsx",
    "chars": 1671,
    "preview": "\n\"use client\";\n\nimport { useState } from \"react\";\nimport { X } from \"lucide-react\";\nimport Link from \"next/link\";\n\nexpor"
  },
  {
    "path": "components/TradingViewWidget.tsx",
    "chars": 2879,
    "preview": "'use client';\n\nimport React, { memo, useState, useEffect } from 'react';\nimport useTradingViewWidget from \"@/hooks/useTr"
  },
  {
    "path": "components/UserDropdown.tsx",
    "chars": 3589,
    "preview": "'use client';\n\nimport {\n    DropdownMenu,\n    DropdownMenuContent,\n    DropdownMenuItem,\n    DropdownMenuLabel,\n    Drop"
  },
  {
    "path": "components/WatchlistButton.tsx",
    "chars": 4616,
    "preview": "\"use client\";\nimport React, { useMemo, useState } from \"react\";\nimport { addToWatchlist, removeFromWatchlist } from \"@/l"
  },
  {
    "path": "components/forms/CountrySelectField.tsx",
    "chars": 5363,
    "preview": "/* eslint-disable @typescript-eslint/no-explicit-any */\n'use client';\n\nimport { useState } from 'react';\nimport { Contro"
  },
  {
    "path": "components/forms/FooterLink.tsx",
    "chars": 438,
    "preview": "import React from 'react'\nimport Link from \"next/link\";\n\nconst FooterLink = ({text, linkText, href}: FooterLinkProps)  ="
  },
  {
    "path": "components/forms/InputField.tsx",
    "chars": 884,
    "preview": "import React from 'react'\nimport {Label} from \"@/components/ui/label\";\nimport {Input} from \"@/components/ui/input\";\nimpo"
  },
  {
    "path": "components/forms/SelectField.tsx",
    "chars": 1615,
    "preview": "import React from 'react'\nimport {Label} from \"@/components/ui/label\";\nimport {Controller} from \"react-hook-form\";\nimpor"
  },
  {
    "path": "components/ui/avatar.tsx",
    "chars": 1097,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as AvatarPrimitive from \"@radix-ui/react-avatar\"\n\nimport { cn } fr"
  },
  {
    "path": "components/ui/button.tsx",
    "chars": 2083,
    "preview": "import * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport { cva, type VariantProps } from \"class"
  },
  {
    "path": "components/ui/command.tsx",
    "chars": 4818,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport { Command as CommandPrimitive } from \"cmdk\"\nimport { SearchIcon } fr"
  },
  {
    "path": "components/ui/dialog.tsx",
    "chars": 3982,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as DialogPrimitive from \"@radix-ui/react-dialog\"\nimport { XIcon } "
  },
  {
    "path": "components/ui/dropdown-menu.tsx",
    "chars": 8284,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as DropdownMenuPrimitive from \"@radix-ui/react-dropdown-menu\"\nimpo"
  },
  {
    "path": "components/ui/input.tsx",
    "chars": 962,
    "preview": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Input({ className, type, ...props }: React.Co"
  },
  {
    "path": "components/ui/label.tsx",
    "chars": 611,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as LabelPrimitive from \"@radix-ui/react-label\"\n\nimport { cn } from"
  },
  {
    "path": "components/ui/popover.tsx",
    "chars": 1635,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as PopoverPrimitive from \"@radix-ui/react-popover\"\n\nimport { cn } "
  },
  {
    "path": "components/ui/select.tsx",
    "chars": 6253,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as SelectPrimitive from \"@radix-ui/react-select\"\nimport { CheckIco"
  },
  {
    "path": "components/ui/sonner.tsx",
    "chars": 564,
    "preview": "\"use client\"\n\nimport { useTheme } from \"next-themes\"\nimport { Toaster as Sonner, ToasterProps } from \"sonner\"\n\nconst Toa"
  },
  {
    "path": "components/watchlist/AlertsPanel.tsx",
    "chars": 3584,
    "preview": "\"use client\";\n\nimport React from \"react\";\nimport { Trash2, TrendingUp, Bell } from \"lucide-react\";\nimport { formatCurren"
  },
  {
    "path": "components/watchlist/CreateAlertModal.tsx",
    "chars": 7843,
    "preview": "\"use client\";\n\nimport React, { useState } from \"react\";\nimport { Dialog, DialogContent, DialogHeader, DialogTitle, Dialo"
  },
  {
    "path": "components/watchlist/NewsGrid.tsx",
    "chars": 2468,
    "preview": "\"use client\";\n\nimport React from \"react\";\nimport Image from \"next/image\";\nimport { formatDistanceToNow } from \"date-fns\""
  },
  {
    "path": "components/watchlist/TradingViewWatchlist.tsx",
    "chars": 2026,
    "preview": "\"use client\";\n\nimport React, { useEffect, useRef, memo } from 'react';\nimport { formatSymbolForTradingView } from '@/lib"
  },
  {
    "path": "components/watchlist/WatchlistManager.tsx",
    "chars": 3979,
    "preview": "'use client';\n\nimport React, { useState, useMemo } from 'react';\nimport WatchlistStockChip from './WatchlistStockChip';\n"
  },
  {
    "path": "components/watchlist/WatchlistStockChip.tsx",
    "chars": 2583,
    "preview": "\"use client\";\n\nimport React, { useState } from \"react\";\nimport { removeFromWatchlist } from \"@/lib/actions/watchlist.act"
  },
  {
    "path": "components/watchlist/WatchlistTable.tsx",
    "chars": 9435,
    "preview": "\"use client\";\n\nimport React, { useEffect, useState } from \"react\";\nimport Image from \"next/image\";\nimport Link from \"nex"
  },
  {
    "path": "components.json",
    "chars": 445,
    "preview": "{\n  \"$schema\": \"https://ui.shadcn.com/schema.json\",\n  \"style\": \"new-york\",\n  \"rsc\": true,\n  \"tsx\": true,\n  \"tailwind\": {"
  },
  {
    "path": "database/models/alert.model.ts",
    "chars": 1087,
    "preview": "import { Schema, model, models, type Document, type Model } from 'mongoose';\n\nexport interface IAlert extends Document {"
  },
  {
    "path": "database/models/watchlist.model.ts",
    "chars": 817,
    "preview": "import { Schema, model, models, type Document, type Model } from 'mongoose';\n\nexport interface WatchlistItem extends Doc"
  },
  {
    "path": "database/mongoose.ts",
    "chars": 1291,
    "preview": "import mongoose from \"mongoose\";\n\nconst MONGODB_URI = process.env.MONGODB_URI;\n\n// FIX: Set Google DNS and force IPv4 to"
  },
  {
    "path": "docker-compose.yml",
    "chars": 644,
    "preview": "services:\n  openstock:\n    build:\n      context: .\n      extra_hosts:\n        - \"mongodb:host-gateway\"\n    ports:\n      "
  },
  {
    "path": "eslint.config.mjs",
    "chars": 524,
    "preview": "import { dirname } from \"path\";\nimport { fileURLToPath } from \"url\";\nimport { FlatCompat } from \"@eslint/eslintrc\";\n\ncon"
  },
  {
    "path": "hooks/useDebounce.ts",
    "chars": 520,
    "preview": "'use client';\n\nimport { useCallback, useRef } from 'react';\n\nexport function useDebounce(callback: () => void, delay: nu"
  },
  {
    "path": "hooks/useTradingViewWidget.tsx",
    "chars": 1299,
    "preview": "'use client';\nimport { useEffect, useRef } from \"react\";\n\nconst useTradingViewWidget = (scriptUrl: string, config: Recor"
  },
  {
    "path": "lib/actions/alert.actions.ts",
    "chars": 1964,
    "preview": "'use server';\n\nimport { connectToDatabase } from '@/database/mongoose';\nimport { Alert, type IAlert } from '@/database/m"
  },
  {
    "path": "lib/actions/auth.actions.ts",
    "chars": 2436,
    "preview": "'use server';\n\nimport { auth } from \"@/lib/better-auth/auth\";\nimport { inngest } from \"@/lib/inngest/client\";\nimport { h"
  },
  {
    "path": "lib/actions/finnhub.actions.ts",
    "chars": 9558,
    "preview": "'use server';\n\nimport { getDateRange, validateArticle, formatArticle } from '@/lib/utils';\nimport { POPULAR_STOCK_SYMBOL"
  },
  {
    "path": "lib/actions/user.actions.ts",
    "chars": 819,
    "preview": "'use server';\n\nimport {connectToDatabase} from \"@/database/mongoose\";\n\nexport const getAllUsersForNewsEmail = async () ="
  },
  {
    "path": "lib/actions/watchlist.actions.ts",
    "chars": 3099,
    "preview": "'use server';\n\nimport { connectToDatabase } from '@/database/mongoose';\nimport { Watchlist } from '@/database/models/wat"
  },
  {
    "path": "lib/better-auth/auth.ts",
    "chars": 1058,
    "preview": "import { betterAuth } from \"better-auth\";\nimport {mongodbAdapter} from \"better-auth/adapters/mongodb\";\nimport {connectTo"
  },
  {
    "path": "lib/constants.ts",
    "chars": 9295,
    "preview": "export const NAV_ITEMS = [\n    { href: '/', label: 'Dashboard' },\n    { href: '/search', label: 'Search' },\n    { href: "
  },
  {
    "path": "lib/inngest/client.ts",
    "chars": 243,
    "preview": "import {Inngest} from \"inngest\"\n\nexport const inngest = new Inngest({\n    id: \"openStock\",\n    ai: {gemini: {apiKey: pro"
  },
  {
    "path": "lib/inngest/functions.ts",
    "chars": 27519,
    "preview": "import { inngest } from \"@/lib/inngest/client\";\nimport { NEWS_SUMMARY_EMAIL_PROMPT, PERSONALIZED_WELCOME_EMAIL_PROMPT } "
  },
  {
    "path": "lib/inngest/prompts.ts",
    "chars": 14547,
    "preview": "export const PERSONALIZED_WELCOME_EMAIL_PROMPT = `Generate highly personalized HTML content that will be inserted into a"
  },
  {
    "path": "lib/kit.ts",
    "chars": 4721,
    "preview": "const KIT_API_URL = 'https://api.kit.com/v4';\n\ninterface KitConfig {\n    apiKey: string;\n    apiSecret: string;\n}\n\nconst"
  },
  {
    "path": "lib/nodemailer/index.ts",
    "chars": 2950,
    "preview": "import nodemailer from 'nodemailer';\nimport {WELCOME_EMAIL_TEMPLATE, NEWS_SUMMARY_EMAIL_TEMPLATE} from \"@/lib/nodemailer"
  },
  {
    "path": "lib/nodemailer/templates.ts",
    "chars": 55304,
    "preview": "export const WELCOME_EMAIL_TEMPLATE = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name"
  },
  {
    "path": "lib/utils.ts",
    "chars": 5885,
    "preview": "import { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\nexport function cn(...inputs: C"
  },
  {
    "path": "middleware/index.ts",
    "chars": 566,
    "preview": "import { NextRequest, NextResponse } from 'next/server';\nimport { getSessionCookie } from \"better-auth/cookies\";\n\nexport"
  },
  {
    "path": "next.config.ts",
    "chars": 663,
    "preview": "import type { NextConfig } from \"next\";\n\nconst nextConfig: NextConfig = {\n    devIndicators: false,\n    /* config option"
  },
  {
    "path": "package.json",
    "chars": 1609,
    "preview": "{\n  \"name\": \"Openstock\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"next dev --turbopack\",\n    "
  },
  {
    "path": "postcss.config.mjs",
    "chars": 81,
    "preview": "const config = {\n  plugins: [\"@tailwindcss/postcss\"],\n};\n\nexport default config;\n"
  },
  {
    "path": "scripts/check-env.mjs",
    "chars": 3205,
    "preview": "#!/usr/bin/env node\n\n/**\n * Environment Variables Checker\n * Run: node scripts/check-env.mjs\n */\n\nconst requiredVars = {"
  },
  {
    "path": "scripts/check_db_name.js",
    "chars": 1067,
    "preview": "\nconst mongoose = require('mongoose');\nrequire('dotenv').config({ path: '.env' });\nconst dns = require('dns');\n// FIX fo"
  },
  {
    "path": "scripts/create-kit-tag.mjs",
    "chars": 850,
    "preview": "\nimport dotenv from 'dotenv';\ndotenv.config({ path: '.env' });\n\nconst KIT_API_KEY = process.env.KIT_API_KEY;\n\nasync func"
  },
  {
    "path": "scripts/inspect-user.mjs",
    "chars": 801,
    "preview": "\nimport { MongoClient } from 'mongodb';\nimport dotenv from 'dotenv';\ndotenv.config({ path: '.env' });\n\nasync function ch"
  },
  {
    "path": "scripts/list-kit-forms.mjs",
    "chars": 681,
    "preview": "\nimport dotenv from 'dotenv';\ndotenv.config({ path: '.env' });\n\nconst KIT_API_KEY = process.env.KIT_API_KEY;\n\nasync func"
  },
  {
    "path": "scripts/migrate-users-to-kit.mjs",
    "chars": 4306,
    "preview": "\nimport dotenv from 'dotenv';\nimport mongoose from 'mongoose';\nimport dns from 'dns';\nimport fetch from 'node-fetch'; //"
  },
  {
    "path": "scripts/resolve_srv.js",
    "chars": 2053,
    "preview": "\nconst dns = require('dns');\nconst { promisify } = require('util');\n\n// Force Google DNS\ndns.setServers(['8.8.8.8']);\n\nc"
  },
  {
    "path": "scripts/seed-inactive-user.mjs",
    "chars": 1553,
    "preview": "\nimport dotenv from 'dotenv';\nimport mongoose from 'mongoose';\nimport dns from 'dns';\ndotenv.config({ path: '.env' });\n\n"
  },
  {
    "path": "scripts/test-db.mjs",
    "chars": 1285,
    "preview": "import 'dotenv/config';\nimport mongoose from 'mongoose';\nimport dns from 'dns';\n\ntry {\n    dns.setServers(['8.8.8.8']);\n"
  },
  {
    "path": "scripts/test-db.ts",
    "chars": 436,
    "preview": "import { connectToDatabase } from \"../database/mongoose\";\n\nasync function main() {\n    try {\n        await connectToData"
  },
  {
    "path": "scripts/test-kit.mjs",
    "chars": 1260,
    "preview": "\nimport dotenv from 'dotenv';\ndotenv.config({ path: '.env' });\n\nconst KIT_API_KEY = process.env.KIT_API_KEY;\nconst KIT_A"
  },
  {
    "path": "scripts/verify-watchlist.mjs",
    "chars": 2215,
    "preview": "import 'dotenv/config';\nimport mongoose from 'mongoose';\nimport { addToWatchlist, removeFromWatchlist, getUserWatchlist,"
  },
  {
    "path": "tsconfig.json",
    "chars": 598,
    "preview": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2017\",\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"allowJs\": true,\n    "
  },
  {
    "path": "types/global.d.ts",
    "chars": 4675,
    "preview": "declare global {\n    type SignInFormData = {\n        email: string;\n        password: string;\n    };\n\n    type SignUpFor"
  }
]

About this extraction

This page contains the full source code of the Open-Dev-Society/OpenStock GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 97 files (442.2 KB), approximately 122.9k tokens, and a symbol index with 200 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!