Compare commits

...
2 Commits
Author SHA1 Message Date
Clive e5d1446791 Site pages and AI tasks.
Gitea CI/CD / build (push) Successful in 1m40s
2026-08-20 14:58:52 +10:00
Clive 152a998311 TailwindCSS production. 2026-08-19 22:22:56 +10:00
56 changed files with 4081 additions and 51 deletions
+141
View File
@@ -0,0 +1,141 @@
---
name: find-skills
description: Helps users discover and install agent skills when they ask questions like "how do I do X", "find a skill for X", "is there a skill that can...", or express interest in extending capabilities. This skill should be used when the user is looking for functionality that might exist as an installable skill.
---
# Find Skills
This skill helps you discover and install skills from the open agent skills ecosystem.
## When to Use This Skill
Use this skill when the user:
- Asks "how do I do X" where X might be a common task with an existing skill
- Says "find a skill for X" or "is there a skill for X"
- Asks "can you do X" where X is a specialized capability
- Expresses interest in extending agent capabilities
- Wants to search for tools, templates, or workflows
- Mentions they wish they had help with a specific domain (design, testing, deployment, etc.)
## What is the Skills CLI?
The Skills CLI (`npx skills`) is the package manager for the open agent skills ecosystem. Skills are modular packages that extend agent capabilities with specialized knowledge, workflows, and tools.
**Key commands:**
- `npx skills find [query] [--owner <owner>]` - Search for skills interactively or by keyword, optionally scoped to a GitHub owner
- `npx skills add <package>` - Install a skill from GitHub or other sources
- `npx skills update` - Update all installed skills
**Browse skills at:** https://skills.sh/
## How to Help Users Find Skills
### Step 1: Understand What They Need
When a user asks for help with something, identify:
1. The domain (e.g., React, testing, design, deployment)
2. The specific task (e.g., writing tests, creating animations, reviewing PRs)
3. Whether this is a common enough task that a skill likely exists
### Step 2: Check the Leaderboard First
Before running a CLI search, check the [skills.sh leaderboard](https://skills.sh/) to see if a well-known skill already exists for the domain. The leaderboard ranks skills by total installs, surfacing the most popular and battle-tested options.
For example, top skills for web development include:
- `vercel-labs/agent-skills` — React, Next.js, web design (100K+ installs each)
- `anthropics/skills` — Frontend design, document processing (100K+ installs)
### Step 3: Search for Skills
If the leaderboard doesn't cover the user's need, run the find command:
```bash
npx skills find [query] [--owner <owner>]
```
For example:
- User asks "how do I make my React app faster?" → `npx skills find react performance`
- User asks "can you help me with PR reviews?" → `npx skills find pr review`
- User asks "I need to create a changelog" → `npx skills find changelog`
### Step 4: Verify Quality Before Recommending
**Do not recommend a skill based solely on search results.** Always verify:
1. **Install count** — Prefer skills with 1K+ installs. Be cautious with anything under 100.
2. **Source reputation** — Official sources (`vercel-labs`, `anthropics`, `microsoft`) are more trustworthy than unknown authors.
3. **GitHub stars** — Check the source repository. A skill from a repo with <100 stars should be treated with skepticism.
### Step 5: Present Options to the User
When you find relevant skills, present them to the user with:
1. The skill name and what it does
2. The install count and source
3. The install command they can run
4. A link to learn more at skills.sh
Example response:
```
I found a skill that might help! The "react-best-practices" skill provides
React and Next.js performance optimization guidelines from Vercel Engineering.
(185K installs)
To install it:
npx skills add vercel-labs/agent-skills@react-best-practices
Learn more: https://skills.sh/vercel-labs/agent-skills/react-best-practices
```
### Step 6: Offer to Install
If the user wants to proceed, you can install the skill for them:
```bash
npx skills add <owner/repo@skill> -g -y
```
The `-g` flag installs globally (user-level) and `-y` skips confirmation prompts.
## Common Skill Categories
When searching, consider these common categories:
| Category | Example Queries |
| --------------- | ---------------------------------------- |
| Web Development | react, nextjs, typescript, css, tailwind |
| Testing | testing, jest, playwright, e2e |
| DevOps | deploy, docker, kubernetes, ci-cd |
| Documentation | docs, readme, changelog, api-docs |
| Code Quality | review, lint, refactor, best-practices |
| Design | ui, ux, design-system, accessibility |
| Productivity | workflow, automation, git |
## Tips for Effective Searches
1. **Use specific keywords**: "react testing" is better than just "testing"
2. **Try alternative terms**: If "deploy" doesn't work, try "deployment" or "ci-cd"
3. **Check popular sources**: Many skills come from `vercel-labs/agent-skills` or `ComposioHQ/awesome-claude-skills`
## When No Skills Are Found
If no relevant skills exist:
1. Acknowledge that no existing skill was found
2. Offer to help with the task directly using your general capabilities
3. Suggest the user could create their own skill with `npx skills init`
Example:
```
I searched for skills related to "xyz" but didn't find any matches.
I can still help you with this task directly! Would you like me to proceed?
If this is something you do often, you could create your own skill:
npx skills init my-xyz-skill
```
+177
View File
@@ -0,0 +1,177 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
+55
View File
@@ -0,0 +1,55 @@
---
name: frontend-design
description: Guidance for distinctive, intentional visual design when building new UI or reshaping an existing one. Helps with aesthetic direction, typography, and making choices that don't read as templated defaults.
license: Complete terms in LICENSE.txt
---
# Frontend Design
Approach this as the design lead at a small studio known for giving every client a visual identity that could not be mistaken for anyone else's. This client has already rejected proposals that felt templated, and is paying for a distinctive point of view: make deliberate, opinionated choices about palette, typography, and layout that are specific to this brief, and take one real aesthetic risk you can justify.
## Ground it in the subject
If the brief does not pin down what the product or subject is, pin it yourself before designing: name one concrete subject, its audience, and the page's single job, and state your choice. If there's any information in your memory about the human's preferences, context about what they're building, or designs you've made before – use that as a hint. The subject's own world, its materials, instruments, artifacts, and vernacular, is where distinctive choices come from. Build with the brief's real content and subject matter throughout.
## Design principles
For web designs, the hero is a thesis. Open with the most characteristic thing in the subject's world, in whatever form makes sense for it: a headline, an image, an animation, a live demo, an interactive moment. Be deliberate with your choice: a big number with a small label, supporting stats, and a gradient accent is the template answer, only use if that's truly the best option.
Typography carries the personality of the page. Pair the display and body faces deliberately, not the same families you would reach for on any other project, and set a clear type scale with intentional weights, widths, and spacing. Make the type treatment itself a memorable part of the design, not a neutral delivery vehicle for the content.
Structure is information. Structural devices, numbering, eyebrows, dividers, labels, should encode something true about the content, not decorate it. Many generic designs use numbered markers (01 / 02 / 03), but that's only appropriate if the content actually is a sequence - like a real process or a typed timeline where order carries information the reader needs. Question if choices like numbered markers actually make sense before incorporating them.
Leverage motion deliberately. Think about where and if animation can serve the subject: a page-load sequence, a scroll-triggered reveal, hover micro-interactions, ambient atmosphere. An orchestrated moment usually lands harder than scattered effects; choose what the direction calls for. However, sometimes less is more, and extra animation contributes to the feeling that the design is AI-generated.
Match complexity to the vision. Maximalist directions need elaborate execution; minimal directions need precision in spacing, type, and detail. Elegance is executing the chosen vision well.
Consider written content carefully. Often a design brief may not contain real content, and it's up to you to come up with copy. Copy can make a design feel as templated as the design itself. See the below section on writing for more guidance.
## Process: brainstorm, explore, plan, critique, build, critique again
For calibration: AI-generated design right now clusters around three looks: (1) a warm cream background (near #F4F1EA) with a high-contrast serif display and a terracotta accent; (2) a near-black background with a single bright acid-green or vermilion accent; (3) a broadsheet-style layout with hairline rules, zero border-radius, and dense newspaper-like columns. All three are legitimate for some briefs, but they are defaults rather than choices, and they appear regardless of subject. Where the brief pins down a visual direction, follow it exactly — the brief's own words always win, including when it asks for one of these looks. Where it leaves an axis free, don't spend that freedom on one of these defaults. Just like a human designer who's hired, there's often a careful balance between doing what you're good at and taking each project as a chance to experiment and learn.
Work in two passes. First, brainstorm a short design plan based on the human's design brief: create a compact token system with color, type, layout, and signature. Color: describe the palette as 4–6 named hex values. Type: the typefaces for 2+ roles (a characterful display face that's used with restraint, a complementary body face, and a utility face for captions or data if needed). Layout: a layout concept, using one-sentence prose descriptions and ASCII wireframes to ideate and compare. Signature: the single unique element this page will be remembered by that embodies the brief in an appropriate way.
Then review that plan against the brief before building: if any part of it reads like the generic default you would produce for any similar page (work through a similar prompt to see if you arrive somewhere similar) rather than a choice made for this specific brief — revise that part, say what you changed and why. Only after you've confirmed the relative uniqueness of your design plan should you start to write the code, following the revised plan exactly and deriving every color and type decision from it.
When writing the code, be careful of structuring your CSS selector specificities. It's easy to generate CSS classes that cancel each other out (especially with a type-based selector like .section and a element-based selector like .cta). This can happen often with paddings/margins between sections.
Try to do a lot of this planning and iteration in your thinking, and only show ideas to the user when you have higher confidence it'll delight them.
## Restraint and self-critique
Spend your boldness in one place. Let the signature element be the one memorable thing, keep everything around it quiet and disciplined, and cut any decoration that does not serve the brief. Not taking a risk can be a risk itself! Build to a quality floor without announcing it: responsive down to mobile, visible keyboard focus, reduced motion respected. Critique your own work as you build, taking screenshots if your environment supports it – a picture is worth 1000 tokens. Consider Chanel's advice: before leaving the house, take a look in the mirror and remove one accessory. Human creators have memory and always try to do something new, so if you have a space to quickly jot down notes about what you've tried, it can help you in future passes.
## More on writing in design
Words appear in a design for one reason: to make it easier to understand, and therefore easier to use. They are design material, not decoration. Bring the same intentionality to copy that you would bring to spacing and color. Before writing anything, ask what the design needs to say, and how it can best be said to help the person navigate the experience.
Write from the end user's side of the screen. Name things by what people control and recognize, never by how the system is built. A person manages notifications, not webhook config. Describe what something does in plain terms rather than selling it. Being specific is always better than being clever.
Use active voice as default. A control should say exactly what happens when it's used: "Save changes," not "Submit." An action keeps the same name through the whole flow, so the button that says "Publish" produces a toast that says "Published." The vocabulary of an interface is the signposting for someone navigating the product. Cohesion and consistency are how people learn their way around.
Treat failure and emptiness as moments for direction, not mood. Explain what went wrong and how to fix it, in the interface's voice rather than a person's. Errors don't apologize, and they are never vague about what happened. An empty screen is an invitation to act.
Keep the register conversational and tuned: plain verbs, sentence case, no filler, with tone matched to the brand and the audience. Let each element do exactly one job. A label labels, an example demonstrates, and nothing quietly does double duty.
+3
View File
@@ -285,6 +285,9 @@ FakesAssemblies/
.ntvs_analysis.dat
node_modules/
# Tailwind CSS build output (generated by `npm run build:css`)
plushealthtech/wwwroot/css/tailwind.css
# Visual Studio 6 build log
*.plg
+10 -8
View File
@@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Overview
plushealthtech is a corporate landing site: a single-project ASP.NET Core Razor Pages app on .NET 10 (`net10.0`). There are no controllers, database, auth, or external APIs — all content lives in Razor Pages and static assets under `wwwroot`.
plushealthtech is a corporate landing site: a single-project ASP.NET Core Razor Pages app on .NET 10 (`net10.0`). There are no controllers, database, auth, or external APIs — all content lives in Razor Pages and static assets under `wwwroot`. The company is Plus Health Tech: interoperable Clinical Decision Support (CDS), Patient Management software, and Veterinary tech (see `Design.MD` for brand positioning).
## Commands
@@ -12,20 +12,22 @@ Run from the repo root (the solution directory).
- Build: `dotnet build plushealthtech.sln`
- Run locally: `dotnet run --project plushealthtech` → https://localhost:7070 (http: 5047, per the `https` profile in `Properties/launchSettings.json`); use `dotnet watch --project plushealthtech` for hot reload
- Docker image (build context is the repo root, not the project folder): `docker build . -t plushealthtech:latest -f plushealthtech/Dockerfile`
- CSS: `npm install` once, then `npm run build:css` compiles the Tailwind v4 bundle into `plushealthtech/wwwroot/css/tailwind.css` (use `npm run watch:css` while editing pages). The layout links this local file — never load the Tailwind Play CDN from markup.
- Docker image (build context is the repo root, not the project folder): `docker build . -t plushealthtech:latest -f plushealthtech/Dockerfile` — the Dockerfile's node stage builds the CSS itself, so no local `npm run build:css` is needed before a Docker build.
- There is no test project and no linter/formatter configuration.
## Architecture
- `plushealthtech/` is the only project. `Program.cs` is the entire pipeline: `AddRazorPages()`, HTTPS redirect, static files — new services would be registered there.
- Pages use the Razor Pages convention: `Pages/<Name>.cshtml` + `<Name>.cshtml.cs` (a PageModel). Existing: `Index` (logo hero), `Privacy`, `Error`, and the shared `Shared/_Layout.cshtml`. There are no areas or controllers.
- Shared chrome and branding: `Pages/Shared/_Layout.cshtml` (navbar with Home + external blog link, footer) and `wwwroot/css/site.css` (CSS custom properties; `.bg-blue` = `#1c8098` is the brand color used on the navbar). The layout `<head>` loads an external analytics script from analytics.rokoh.com.
- Pages use the Razor Pages convention: `Pages/<Name>.cshtml` + `<Name>.cshtml.cs` (a PageModel). Existing: `Index` (the full landing page: hero, `#solutions` cards incl. a `#veterinary` card, `#interoperability` section), `Privacy`, `Error`, and the shared `Shared/_Layout.cshtml`. There are no areas or controllers.
- Shared chrome: `Pages/Shared/_Layout.cshtml` — sticky glassmorphism navbar (anchor links `#solutions`/`#interoperability`/`#veterinary`/`#about`, external blog link to blog.plushealthtech.com, Sign In / Request Demo CTAs pointing at `#contact`, which is the footer's id), and a dark footer. Note the **header logo is hotlinked from the production domain** (`https://www.plushealthtech.com/img/plushealthtech-logo.png`) while the footer uses the local `~/img/` copy.
- Styling is a **Tailwind v4** bundle only: source `wwwroot/css/tailwind.input.css` (CSS-first `@theme` with brand colors — `brand` `#1c8098`, `brand-dark` `#145f72`, `brand-light` `#e6f2f5`, `brand-accent` `#0ea5e9`, `brand-vet` `#0d9488`), built by the Tailwind CLI into `wwwroot/css/tailwind.css` and linked in the layout `<head>`. `wwwroot/css/site.css`, `wwwroot/js/site.js`, and the vendored Bootstrap/jQuery in `wwwroot/lib` are **template leftovers linked from no page** — don't build on them.
- External runtime dependencies in the layout `<head>`: Font Awesome 6.4.0 from cdnjs (icons used throughout the markup) and an analytics script from analytics.rokoh.com.
- Brand and UI design guidelines live in `Design.MD` (color palette with `#1c8098` as brand core, typography, component/layout conventions) — follow it when adding or restyling pages.
- Client libraries (Bootstrap, jQuery, jquery-validation) are vendored in `wwwroot/lib`; logo and favicon are in `wwwroot/img/`.
## Deployment
- Pushes to Gitea (gitea.rokoh.com) run the Gitea Actions workflow: build the Docker image → push `registry.rokoh.com/plushealthtech:latest` → SSH into `10.10.0.90` and run the container with `-p 7000:80 --restart=always`. Registry and server credentials come from Gitea secrets, not the repo.
- Pushes to Gitea (gitea.rokoh.com) run the Gitea Actions workflow: build the Docker image → push `registry.rokoh.com/plushealthtech:latest` → SSH into `10.10.0.90` and run the container with `-p 7000:8080 --restart=always`. **The app listens on 8080 inside the container** (the .NET aspnet image's default `ASPNETCORE_HTTP_PORTS`); the Dockerfile's `EXPOSE 80/443` is stale and not the port the container serves. Don't "fix" the workflow to 80 to match the EXPOSE.
- Registry and server credentials come from Gitea secrets, not the repo.
- The workflow file exists in **two identical copies**: `.gitea/workflows/default.yml` (the one Gitea reads) and `plushealthtech/.gitea/workflows/default.yml` (referenced from the csproj as `<None Include>`). Keep them in sync when editing either.
- The Dockerfile is multi-stage (restore → build → publish → aspnet runtime), exposes 80/443, entrypoint `dotnet plushealthtech.dll`.
- **Known inconsistency**: the project targets `net10.0`, but the Dockerfile still pins .NET 7 base images (`mcr.microsoft.com/dotnet/{aspnet,sdk}:7.0`), so `docker build` fails as-is. When touching the Dockerfile, bump the base images to .NET 10 to match the TFM.
- The Dockerfile is multi-stage (node `frontend` builds CSS → `build` → `publish` with `UseAppHost=false` → `final` runtime), entrypoint `dotnet plushealthtech.dll`. The `frontend` stage runs `npm ci && npm run build:css` and feeds the generated `wwwroot/css/tailwind.css` into the dotnet build, so the image always ships freshly compiled CSS.
+5
View File
@@ -0,0 +1,5 @@
# Netscape HTTP Cookie File
# https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.
#HttpOnly_localhost FALSE / FALSE 0 .AspNetCore.Antiforgery.wRylZNat_hI CfDJ8LGyuqR4T-5Fs6kPeIwHZi_d7o1i8jGqa9T8fS8yz2y9SPXl5luQoltl_9zXP0-VKwWcj0S866UE4uvN1jEH3kCZ2kC6ZTnryE8ybS3EdbzE8R8W4OhKV_uoioIWhzjiMNMJLHIcu2ArZlZf8T-Izj0
+1170
View File
File diff suppressed because it is too large Load Diff
+12
View File
@@ -0,0 +1,12 @@
{
"name": "plushealthtech",
"private": true,
"scripts": {
"build:css": "tailwindcss -i ./plushealthtech/wwwroot/css/tailwind.input.css -o ./plushealthtech/wwwroot/css/tailwind.css --minify",
"watch:css": "tailwindcss -i ./plushealthtech/wwwroot/css/tailwind.input.css -o ./plushealthtech/wwwroot/css/tailwind.css --watch"
},
"devDependencies": {
"@tailwindcss/cli": "^4.1.0",
"tailwindcss": "^4.1.0"
}
}
+123
View File
@@ -0,0 +1,123 @@
# Site Expansion Plan — New Pages, Forms & MailPit Delivery
Status: Ready for execution — 2026-08-20
## 1. Objective
Grow the Plus Health Tech site from a single landing page into a multi-page site:
- Add **10 linked pages**: About Us, Careers, Contact, Privacy Policy, Terms & Conditions, Sign In, Request Demo, Clinical Decision Support, Patient Management, Veterinarian Solutions.
- **Contact** and **Request Demo** get forms that send email via plain SMTP (no credentials) to a MailPit server at **`tools.host.domain:1025`**, recipient **`[email protected]`**.
- Wire every page into the navbar, footer, and the Index hero / solution-card CTAs (no dead `#` links left).
## 2. Current state (audited 2026-08-20)
- `dotnet build plushealthtech.sln` — **green** (0 warnings / 0 errors).
- Pages that exist: `Index` (landing), `Privacy` (template placeholder text), `Error`.
- **In-progress work already in the working tree** (do not recreate):
- `plushealthtech/Mail/MailSender.cs` — `SmtpClient`, `EnableSsl = false`, no auth, `SendAsync(subject, body)` from `SmtpOptions`.
- `plushealthtech/Mail/SmtpOptions.cs` — binds the `"Smtp"` config section (Host/Port/From/To; From/To default to empty).
- `Program.cs` — already registers `Configure<SmtpOptions>("Smtp")` + `AddSingleton<MailSender>()`.
- `Pages/ClinicalDecisionSupport.cshtml.cs` — PageModel exists, **the `.cshtml` view is missing** (route 404s until added).
- **Gap**: no `"Smtp"` section in `appsettings.json` → `From`/`To` are empty → a form submission today would throw in `MailSender`.
- Link rot today: navbar `Sign In` / `Request Demo` → `#contact` (footer id); footer links all → `#`; Index hero CTA → `#contact`; solution-card "Learn more" links → `#`.
- Mobile menu button has **no JS** (no-op) — pre-existing defect worth fixing while we touch the nav.
- Styling: Tailwind v4 local bundle only; brand tokens (`brand`, `brand-dark`, `brand-light`, `brand-accent`, `brand-vet`) already defined.
## 3. Decisions & assumptions
1. **No new dependencies.** Razor Pages + `System.Net.Mail.SmtpClient` (already in the shared framework) + DataAnnotations validation. No auth package.
2. **Reuse the existing `Mail/` infrastructure** as-is; only the missing config section is added.
3. **SMTP target**: `tools.host.domain:1025`, no TLS, no credentials (MailPit). Recipient `[email protected]`. **From: `[email protected]`** (assumed — MailPit doesn't validate the sender; it lives in one place, `appsettings.json`, easy to change).
4. **Sign In is a placeholder page.** The repo has no auth (per CLAUDE.md); the page says sign-in is coming soon and offers Request Demo / Contact CTAs. Real authentication is a separate project.
5. **Privacy Policy keeps its existing route** `/Privacy` (page already exists); only the placeholder content is replaced.
6. **Copy** for About / Careers / Terms / Privacy is standard corporate boilerplate aligned to `Design.MD` — flagged for a legal/brand review before production.
7. **Mobile menu gets a minimal vanilla-JS toggle** so the new nav links are actually reachable on small screens.
8. **Footer newsletter input stays decorative** — no endpoint was specified for it; out of scope.
## 4. Route map
| Page | File(s) | Route |
|---|---|---|
| Clinical Decision Support | `Pages/ClinicalDecisionSupport.cshtml` (`.cs` already exists) | `/ClinicalDecisionSupport` |
| Patient Management | `Pages/PatientManagement.cshtml` + `.cs` | `/PatientManagement` |
| Veterinarian Solutions | `Pages/Veterinary.cshtml` + `.cs` | `/Veterinary` |
| About Us | `Pages/About.cshtml` + `.cs` | `/About` |
| Careers | `Pages/Careers.cshtml` + `.cs` | `/Careers` |
| Contact (form) | `Pages/Contact.cshtml` + `.cs` | `/Contact` |
| Request Demo (form) | `Pages/RequestDemo.cshtml` + `.cs` | `/RequestDemo` |
| Sign In (placeholder) | `Pages/SignIn.cshtml` + `.cs` | `/SignIn` |
| Terms & Conditions | `Pages/Terms.cshtml` + `.cs` | `/Terms` |
| Privacy Policy (content refresh) | `Pages/Privacy.cshtml` (existing) | `/Privacy` |
## 5. Page specs (summary)
- **Product pages (CDS, Patient Management, Veterinary)** — hero with one-line value prop; 3–4 feature cards reusing Index card copy + `#interoperability` section copy (FHIR®/HL7v2, alerting, point-of-care guidance); CTA band → `/RequestDemo`. Veterinary page uses the `brand-vet` (`#0d9488`) accent per `Design.MD`.
- **About Us** — mission, three product pillars (each linking to its page), values, CTA → `/RequestDemo`.
- **Careers** — culture & expectations, "how to apply" pointing at `/Contact` (no job-board data exists).
- **Terms & Conditions** — boilerplate sections, including a clinical-software disclaimer (CDS is decision support, not a substitute for professional judgment).
- **Privacy Policy** — data collected by the two forms, where it goes (`[email protected]`), third parties (cdnjs, analytics.rokoh.com), cookies, contact.
- **Sign In** — centered card: sign-in coming soon + CTAs.
- **Contact** — form: Name*, Work Email*, Organization, Phone, Message*. Success panel on send; friendly error on SMTP failure (details logged).
- **Request Demo** — form: Name*, Work Email*, Organization*, Practice Type (select), Products of Interest (select), Message (optional). Same send/success/error behavior.
## 6. Mail spec
- Config in `appsettings.json`:
```json
"Smtp": {
"Host": "tools.host.domain",
"Port": 1025,
"From": "[email protected]",
"To": "[email protected]"
}
```
- Form → `OnPost` (antiforgery token, `ModelState` check) → `MailSender.SendAsync(subject, body)`:
- Contact subject: `New contact form message — {Name}`
- Demo subject: `New demo request — {Organization}`
- Body: plain-text, one `Field: value` line per field, then the message.
- SMTP exception → re-render with a generic "message could not be sent, please email us directly" banner; exception logged, never surfaced.
## 7. Navigation & link plan
- **Desktop nav** (replaces current anchor links): CDS → `/ClinicalDecisionSupport` · Patient Management → `/PatientManagement` · Veterinary → `/Veterinary` · About → `/About` · Careers → `/Careers` · Contact → `/Contact` · Blog (external, keep).
- **CTA buttons**: Sign In → `/SignIn` · Request Demo → `/RequestDemo`.
- **Footer**: Solutions column → 3 product routes; Company column → `/About`, `/Careers`, `/Contact`; new **Legal** column → `/Privacy`, `/Terms`. Remove the footer's `id="contact"` (nothing may target it anymore).
- **Index**: hero "Explore Platform" → `/RequestDemo`; "View Solutions" keeps `#solutions`; the three solution-card links → the product routes.
- **Mobile menu**: small vanilla-JS toggle in the layout (or `<details>`) so nav is reachable on mobile.
## 8. Task list (files in `task/`, in recommended order)
| # | Task file | Scope | Depends on |
|---|---|---|---|
| 01 | `task/01-smtp-mail-configuration.md` | `Smtp` config section; confirm MailPit delivery path | — |
| 02 | `task/02-contact-page.md` | `/Contact` + form | 01 |
| 03 | `task/03-request-demo-page.md` | `/RequestDemo` + form | 01 |
| 04 | `task/04-clinical-decision-support-page.md` | CDS view (PageModel exists) | — |
| 05 | `task/05-patient-management-page.md` | `/PatientManagement` | — |
| 06 | `task/06-veterinary-solutions-page.md` | `/Veterinary` | — |
| 07 | `task/07-about-us-page.md` | `/About` | — |
| 08 | `task/08-careers-page.md` | `/Careers` | — |
| 09 | `task/09-terms-and-conditions-page.md` | `/Terms` | — |
| 10 | `task/10-sign-in-page.md` | `/SignIn` placeholder | — |
| 11 | `task/11-privacy-policy-content.md` | `/Privacy` content refresh | — |
| 12 | `task/12-navigation-footer-cta-links.md` | layout + Index links, mobile menu | 02–11 |
| 13 | `task/13-verification-and-final-checks.md` | build, link sweep, e2e mail test | all |
Tasks 02–11 are independent of each other (02/03 need 01) and can be done in any order or by parallel contributors.
## 9. Verification (task 13)
1. `dotnet build plushealthtech.sln` → 0 errors / 0 warnings.
2. `dotnet run --project plushealthtech` → walk navbar (desktop **and** mobile), footer, Index CTAs; every link resolves (no `#` left except the `#solutions` / `#interoperability` in-page anchors).
3. Submit both forms → messages appear in MailPit (Web UI / `mailpit api`) addressed to `alerts@plushealthtech.local`; app log shows the `Mail sent to …` line; error path (MailPit down) renders the friendly banner.
4. `/Privacy` shows the new policy, not the template text.
5. No changes needed to the Dockerfile / Gitea workflow — the image pipeline already rebuilds CSS and ships everything in `wwwroot` + the app.
## 10. Risks / open items
- `tools.host.domain` must be resolvable from wherever the app runs (local dev vs. the `10.10.0.90` container host). If it only resolves in the internal network, dev machines may need a hosts entry — confirm during task 01.
- Sign In placeholder: if a real portal is planned, that's a separate project (auth + account backend) and page 10 will be the integration point.
- Terms/Privacy copy is boilerplate until reviewed.
+8
View File
@@ -5,11 +5,19 @@ WORKDIR /app
EXPOSE 80
EXPOSE 443
FROM node:24-alpine AS frontend
WORKDIR /src
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build:css
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY ["plushealthtech/plushealthtech.csproj", "plushealthtech/"]
RUN dotnet restore "plushealthtech/plushealthtech.csproj"
COPY . .
COPY --from=frontend /src/plushealthtech/wwwroot/css/tailwind.css ./plushealthtech/wwwroot/css/tailwind.css
WORKDIR "/src/plushealthtech"
RUN dotnet build "plushealthtech.csproj" -c Release -o /app/build
+39
View File
@@ -0,0 +1,39 @@
using System.Net.Mail;
using Microsoft.Extensions.Options;
namespace plushealthtech.Mail
{
/// <summary>
/// Sends outbound email through the configured SMTP server (MailPit in local
/// development). No authentication is used.
/// </summary>
public class MailSender
{
private readonly SmtpOptions _options;
private readonly ILogger<MailSender> _logger;
public MailSender(IOptions<SmtpOptions> options, ILogger<MailSender> logger)
{
_options = options.Value;
_logger = logger;
}
public async Task SendAsync(string subject, string body)
{
using var client = new SmtpClient(_options.Host, _options.Port)
{
EnableSsl = false
};
var message = new MailMessage(_options.From, _options.To)
{
Subject = subject,
Body = body,
IsBodyHtml = false
};
await client.SendMailAsync(message);
_logger.LogInformation("Mail sent to {To} (subject: {Subject})", _options.To, subject);
}
}
}
+13
View File
@@ -0,0 +1,13 @@
namespace plushealthtech.Mail
{
/// <summary>
/// SMTP settings bound from the "Smtp" section of appsettings.json.
/// </summary>
public class SmtpOptions
{
public string Host { get; set; } = "localhost";
public int Port { get; set; } = 1025;
public string From { get; set; } = string.Empty;
public string To { get; set; } = string.Empty;
}
}
+154
View File
@@ -0,0 +1,154 @@
@page
@model AboutModel
@{
ViewData["Title"] = "About Us";
}
<!-- Hero Section -->
<section class="relative overflow-hidden pt-16 pb-20 lg:pt-24 lg:pb-24 bg-gradient-to-b from-white via-slate-50 to-brand-light/30">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
<div class="max-w-3xl mx-auto space-y-4 text-center">
<span class="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-brand-light text-brand text-xs font-bold uppercase tracking-wider">
<i class="fa-solid fa-hospital"></i> About Plus Health Tech
</span>
<h1 class="text-4xl sm:text-5xl font-extrabold text-slate-900 tracking-tight leading-tight">
Clinical software built on <span class="text-brand">trust, precision, and care</span>
</h1>
<p class="text-lg text-slate-600">
Plus Health Tech builds interoperable clinical software for people and the animals they care for — decision support, patient management, and veterinary platforms that stay connected to the systems where care already happens.
</p>
</div>
</div>
</section>
<!-- What We Do -->
<section id="what-we-do" class="py-24 bg-white">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="text-center max-w-3xl mx-auto space-y-4 mb-16">
<h2 class="text-xs font-bold tracking-widest text-brand uppercase">What We Do</h2>
<h3 class="text-3xl font-extrabold text-slate-900 tracking-tight">One platform strategy, three product pillars</h3>
<p class="text-slate-600">Every product we build shares the same foundation: open standards, evidence-based logic, and a deep respect for the people using it at the point of care.</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
<!-- Clinical Decision Support -->
<a href="/ClinicalDecisionSupport" class="group bg-slate-50 p-8 rounded-2xl border border-slate-100 hover:shadow-xl hover:border-brand/20 transition-all flex flex-col">
<div class="w-14 h-14 rounded-xl bg-brand-light text-brand flex items-center justify-center text-xl font-bold mb-6 group-hover:bg-brand group-hover:text-white transition-colors">
<i class="fa-solid fa-brain"></i>
</div>
<h4 class="text-xl font-bold text-slate-900 mb-3">Clinical Decision Support</h4>
<p class="text-slate-600 text-sm leading-relaxed flex-1">
Real-time, evidence-based guidance at the point of care — cross-checking orders, doses, and results the moment they enter the chart.
</p>
<span class="inline-flex items-center gap-2 mt-6 text-sm font-semibold text-brand">
Explore CDS <i class="fa-solid fa-arrow-right group-hover:translate-x-1 transition-transform"></i>
</span>
</a>
<!-- Patient Management -->
<a href="/PatientManagement" class="group bg-slate-50 p-8 rounded-2xl border border-slate-100 hover:shadow-xl hover:border-brand-accent/20 transition-all flex flex-col">
<div class="w-14 h-14 rounded-xl bg-sky-50 text-brand-accent flex items-center justify-center text-xl font-bold mb-6 group-hover:bg-brand-accent group-hover:text-white transition-colors">
<i class="fa-solid fa-clipboard-user"></i>
</div>
<h4 class="text-xl font-bold text-slate-900 mb-3">Patient Management</h4>
<p class="text-slate-600 text-sm leading-relaxed flex-1">
A complete medical home for every patient — records, scheduling, and communication that flow together instead of fragmenting care.
</p>
<span class="inline-flex items-center gap-2 mt-6 text-sm font-semibold text-brand-accent">
Explore Patient Management <i class="fa-solid fa-arrow-right group-hover:translate-x-1 transition-transform"></i>
</span>
</a>
<!-- Veterinary Solutions -->
<a href="/Veterinary" class="group bg-slate-50 p-8 rounded-2xl border border-slate-100 hover:shadow-xl hover:border-brand-vet/20 transition-all flex flex-col">
<div class="w-14 h-14 rounded-xl bg-teal-50 text-brand-vet flex items-center justify-center text-xl font-bold mb-6 group-hover:bg-brand-vet group-hover:text-white transition-colors">
<i class="fa-solid fa-paw"></i>
</div>
<h4 class="text-xl font-bold text-slate-900 mb-3">Veterinarian Solutions</h4>
<p class="text-slate-600 text-sm leading-relaxed flex-1">
Purpose-built software for animal clinics — EHR records, imaging, and species-tailored decision tools that keep practice and care moving as one.
</p>
<span class="inline-flex items-center gap-2 mt-6 text-sm font-semibold text-brand-vet">
Explore Veterinary <i class="fa-solid fa-arrow-right group-hover:translate-x-1 transition-transform"></i>
</span>
</a>
</div>
</div>
</section>
<!-- Values -->
<section id="values" class="py-24 bg-slate-50">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="text-center max-w-3xl mx-auto space-y-4 mb-16">
<h2 class="text-xs font-bold tracking-widest text-brand uppercase">Our Values</h2>
<h3 class="text-3xl font-extrabold text-slate-900 tracking-tight">The principles behind every release</h3>
<p class="text-slate-600">These aren't wall posters — they're the constraints we design against, in every product, from the first architecture decision to the last line of a care alert.</p>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8">
<!-- Value 1 -->
<div class="bg-white p-8 rounded-2xl border border-slate-100 shadow-sm hover:shadow-xl transition-all">
<div class="w-12 h-12 rounded-xl bg-brand-light text-brand flex items-center justify-center text-lg mb-5">
<i class="fa-solid fa-circle-nodes"></i>
</div>
<h4 class="text-lg font-bold text-slate-900 mb-2">Interoperability by default</h4>
<p class="text-slate-600 text-sm leading-relaxed">
FHIR® and HL7v2 are our starting point, not an add-on — your data moves between systems without rip-and-replace or parallel records.
</p>
</div>
<!-- Value 2 -->
<div class="bg-white p-8 rounded-2xl border border-slate-100 shadow-sm hover:shadow-xl transition-all">
<div class="w-12 h-12 rounded-xl bg-brand-light text-brand flex items-center justify-center text-lg mb-5">
<i class="fa-solid fa-flask-vial"></i>
</div>
<h4 class="text-lg font-bold text-slate-900 mb-2">Evidence-based</h4>
<p class="text-slate-600 text-sm leading-relaxed">
Every recommendation traces back to current clinical evidence — not guesswork, not vendor opinion, not a black box.
</p>
</div>
<!-- Value 3 -->
<div class="bg-white p-8 rounded-2xl border border-slate-100 shadow-sm hover:shadow-xl transition-all">
<div class="w-12 h-12 rounded-xl bg-brand-light text-brand flex items-center justify-center text-lg mb-5">
<i class="fa-solid fa-hand-holding-heart"></i>
</div>
<h4 class="text-lg font-bold text-slate-900 mb-2">Patient-first</h4>
<p class="text-slate-600 text-sm leading-relaxed">
Human or animal, the patient is who we build for. The software is in service of the care — never the other way around.
</p>
</div>
<!-- Value 4 -->
<div class="bg-white p-8 rounded-2xl border border-slate-100 shadow-sm hover:shadow-xl transition-all">
<div class="w-12 h-12 rounded-xl bg-brand-light text-brand flex items-center justify-center text-lg mb-5">
<i class="fa-solid fa-user-doctor"></i>
</div>
<h4 class="text-lg font-bold text-slate-900 mb-2">Built with clinicians</h4>
<p class="text-slate-600 text-sm leading-relaxed">
Doctors, nurses, and veterinarians shape our roadmaps — tools that respect how care actually works on a busy day.
</p>
</div>
</div>
</div>
</section>
<!-- CTA Section -->
<section class="py-24 bg-slate-50">
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="bg-gradient-to-br from-brand to-brand-dark text-white rounded-2xl p-10 sm:p-14 shadow-xl shadow-brand/20 text-center space-y-4">
<h2 class="text-3xl font-extrabold tracking-tight">See the platform in action</h2>
<p class="text-slate-200 max-w-2xl mx-auto">
Book a walkthrough with our team and watch decision support, patient management, and interoperability run the way your care setting actually operates.
</p>
<div class="flex flex-col sm:flex-row items-center justify-center gap-4 pt-2">
<a href="/RequestDemo" class="w-full sm:w-auto bg-white text-brand hover:bg-slate-100 font-semibold px-8 py-4 rounded-xl shadow-md transition-all text-center">
Request a demo <i class="fa-solid fa-arrow-right ml-2"></i>
</a>
<a href="/Careers" class="w-full sm:w-auto text-white font-semibold px-8 py-4 rounded-xl border border-white/40 hover:bg-white/10 transition-all text-center">
Build with us
</a>
</div>
</div>
</div>
</section>
+19
View File
@@ -0,0 +1,19 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace plushealthtech.Pages
{
public class AboutModel : PageModel
{
private readonly ILogger<AboutModel> _logger;
public AboutModel(ILogger<AboutModel> logger)
{
_logger = logger;
}
public void OnGet()
{
}
}
}
+102
View File
@@ -0,0 +1,102 @@
@page
@model CareersModel
@{
ViewData["Title"] = "Careers";
}
<!-- Hero Section -->
<section class="relative overflow-hidden pt-16 pb-20 lg:pt-24 lg:pb-24 bg-gradient-to-b from-white via-slate-50 to-brand-light/30">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
<div class="max-w-3xl mx-auto space-y-4 text-center">
<span class="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-brand-light text-brand text-xs font-bold uppercase tracking-wider">
<i class="fa-solid fa-briefcase"></i> Careers at Plus Health Tech
</span>
<h1 class="text-4xl sm:text-5xl font-extrabold text-slate-900 tracking-tight leading-tight">
Build the future of <span class="text-brand">interoperable clinical software</span>
</h1>
<p class="text-lg text-slate-600">
We're a small, product-focused team building decision support, patient management, and veterinary platforms that keep the systems where care happens connected. If that's the kind of work you want to do, we'd love to hear from you.
</p>
</div>
</div>
</section>
<!-- Why Join Us -->
<section id="why-join-us" class="py-24 bg-white">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="text-center max-w-3xl mx-auto space-y-4 mb-16">
<h2 class="text-xs font-bold tracking-widest text-brand uppercase">Why Join Us</h2>
<h3 class="text-3xl font-extrabold text-slate-900 tracking-tight">Do work that matters, on a team that ships</h3>
<p class="text-slate-600">We build software that sits at the point of care, so the bar for quality is high — and the payoff is seeing it used every day. Here's what working with us looks like.</p>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8">
<!-- Card 1 -->
<div class="bg-slate-50 p-8 rounded-2xl border border-slate-100 shadow-sm hover:shadow-xl transition-all group">
<div class="w-12 h-12 rounded-xl bg-brand-light text-brand flex items-center justify-center text-lg mb-5 group-hover:bg-brand group-hover:text-white transition-colors">
<i class="fa-solid fa-bullseye"></i>
</div>
<h4 class="text-lg font-bold text-slate-900 mb-2">Mission-driven work</h4>
<p class="text-slate-600 text-sm leading-relaxed">
The software we build touches real care, every day. You'll see the line between a well-placed piece of code and better decisions at the point of care.
</p>
</div>
<!-- Card 2 -->
<div class="bg-slate-50 p-8 rounded-2xl border border-slate-100 shadow-sm hover:shadow-xl transition-all group">
<div class="w-12 h-12 rounded-xl bg-brand-light text-brand flex items-center justify-center text-lg mb-5 group-hover:bg-brand group-hover:text-white transition-colors">
<i class="fa-solid fa-user-doctor"></i>
</div>
<h4 class="text-lg font-bold text-slate-900 mb-2">Clinical + technical collaboration</h4>
<p class="text-slate-600 text-sm leading-relaxed">
We build alongside clinicians and veterinarians, not just for them — so the tools we ship respect how care actually works on a busy day.
</p>
</div>
<!-- Card 3 -->
<div class="bg-slate-50 p-8 rounded-2xl border border-slate-100 shadow-sm hover:shadow-xl transition-all group">
<div class="w-12 h-12 rounded-xl bg-brand-light text-brand flex items-center justify-center text-lg mb-5 group-hover:bg-brand group-hover:text-white transition-colors">
<i class="fa-solid fa-code"></i>
</div>
<h4 class="text-lg font-bold text-slate-900 mb-2">Modern .NET stack</h4>
<p class="text-slate-600 text-sm leading-relaxed">
A current, well-supported stack — .NET 10, Razor Pages, and Tailwind CSS — so your energy goes into the product, not into fighting the tooling.
</p>
</div>
<!-- Card 4 -->
<div class="bg-slate-50 p-8 rounded-2xl border border-slate-100 shadow-sm hover:shadow-xl transition-all group">
<div class="w-12 h-12 rounded-xl bg-brand-light text-brand flex items-center justify-center text-lg mb-5 group-hover:bg-brand group-hover:text-white transition-colors">
<i class="fa-solid fa-house-laptop"></i>
</div>
<h4 class="text-lg font-bold text-slate-900 mb-2">Ways of working that fit life</h4>
<p class="text-slate-600 text-sm leading-relaxed">
We're pragmatic about how and where work happens, and we keep the process lean so the team can stay focused on shipping quality software.
</p>
</div>
</div>
</div>
</section>
<!-- How to Apply -->
<section id="how-to-apply" class="py-24 bg-slate-50">
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="bg-gradient-to-br from-brand to-brand-dark text-white rounded-2xl p-10 sm:p-14 shadow-xl shadow-brand/20 text-center space-y-5">
<span class="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white text-xs font-bold uppercase tracking-wider">
<i class="fa-solid fa-envelope-open-text"></i> How to Apply
</span>
<h2 class="text-3xl font-extrabold tracking-tight">We're not running a public job board right now</h2>
<p class="text-slate-100 max-w-2xl mx-auto">
Tell us about yourself via the Contact page and we'll be in touch. Whether you have a role in mind or just want to introduce yourself, we read every note.
</p>
<div class="flex flex-col sm:flex-row items-center justify-center gap-4 pt-2">
<a href="/Contact" class="w-full sm:w-auto bg-white text-brand hover:bg-slate-100 font-semibold px-8 py-4 rounded-xl shadow-md transition-all text-center">
Tell us about yourself <i class="fa-solid fa-arrow-right ml-2"></i>
</a>
<a href="/About" class="w-full sm:w-auto text-white font-semibold px-8 py-4 rounded-xl border border-white/40 hover:bg-white/10 transition-all text-center">
Learn about us
</a>
</div>
</div>
</div>
</section>
+19
View File
@@ -0,0 +1,19 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace plushealthtech.Pages
{
public class CareersModel : PageModel
{
private readonly ILogger<CareersModel> _logger;
public CareersModel(ILogger<CareersModel> logger)
{
_logger = logger;
}
public void OnGet()
{
}
}
}
@@ -0,0 +1,150 @@
@page
@model ClinicalDecisionSupportModel
@{
ViewData["Title"] = "Clinical Decision Support";
}
<!-- Hero Section -->
<section class="relative overflow-hidden pt-16 pb-20 lg:pt-24 lg:pb-24 bg-gradient-to-b from-white via-slate-50 to-brand-light/30">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
<div class="max-w-3xl mx-auto space-y-4 text-center">
<span class="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-brand-light text-brand text-xs font-bold uppercase tracking-wider">
<i class="fa-solid fa-brain"></i> Point-of-Care Intelligence
</span>
<h1 class="text-4xl sm:text-5xl font-extrabold text-slate-900 tracking-tight leading-tight">
Evidence-based guidance <span class="text-brand">at the point of care</span>
</h1>
<p class="text-lg text-slate-600">
Our CDS platform cross-checks every order, dose, and result in real time — surfacing the safest, most effective option exactly where care happens.
</p>
<div class="flex flex-col sm:flex-row items-center justify-center gap-4 pt-4">
<a href="/RequestDemo" class="w-full sm:w-auto bg-brand hover:bg-brand-dark text-white font-semibold px-8 py-4 rounded-xl shadow-lg shadow-brand/25 transition-all text-center">
Request a demo <i class="fa-solid fa-arrow-right ml-2"></i>
</a>
<a href="#interoperability" class="w-full sm:w-auto bg-white hover:bg-slate-50 text-slate-700 font-semibold px-8 py-4 rounded-xl border border-slate-200 transition-all text-center">
See how it connects
</a>
</div>
</div>
</div>
</section>
<!-- Capabilities Section -->
<section id="capabilities" class="py-24 bg-white">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="text-center max-w-3xl mx-auto space-y-4 mb-16">
<h2 class="text-xs font-bold tracking-widest text-brand uppercase">Clinical Intelligence</h2>
<h3 class="text-3xl font-extrabold text-slate-900 tracking-tight">Decision support that works the way clinicians do</h3>
<p class="text-slate-600">Real-time recommendations built on live patient data — engineered to catch risk early and keep every treatment choice aligned with current evidence.</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
<!-- Card 1 -->
<div class="bg-slate-50 p-8 rounded-2xl border border-slate-100 hover:shadow-xl transition-all group">
<div class="w-14 h-14 rounded-xl bg-brand-light text-brand flex items-center justify-center text-xl font-bold mb-6 group-hover:bg-brand group-hover:text-white transition-colors">
<i class="fa-solid fa-brain"></i>
</div>
<h4 class="text-xl font-bold text-slate-900 mb-3">Error Reduction by Design</h4>
<p class="text-slate-600 text-sm leading-relaxed">
Every order, dose, and result is cross-checked against intelligent, evidence-based algorithms the moment it enters the chart — flagging unsafe combinations, duplicates, and allergies before they reach the patient.
</p>
</div>
<!-- Card 2 -->
<div class="bg-slate-50 p-8 rounded-2xl border border-slate-100 hover:shadow-xl transition-all group">
<div class="w-14 h-14 rounded-xl bg-brand-light text-brand flex items-center justify-center text-xl font-bold mb-6 group-hover:bg-brand group-hover:text-white transition-colors">
<i class="fa-solid fa-stethoscope"></i>
</div>
<h4 class="text-xl font-bold text-slate-900 mb-3">Optimized Treatment Choices</h4>
<p class="text-slate-600 text-sm leading-relaxed">
Recommendations weigh current guidelines, live lab values, and documented history to surface the therapies most likely to work for the patient in front of you.
</p>
</div>
<!-- Card 3 -->
<div class="bg-slate-50 p-8 rounded-2xl border border-slate-100 hover:shadow-xl transition-all group">
<div class="w-14 h-14 rounded-xl bg-brand-light text-brand flex items-center justify-center text-xl font-bold mb-6 group-hover:bg-brand group-hover:text-white transition-colors">
<i class="fa-solid fa-bell"></i>
</div>
<h4 class="text-xl font-bold text-slate-900 mb-3">Alerts That Fit the Workflow</h4>
<p class="text-slate-600 text-sm leading-relaxed">
Smart, low-noise alerts arrive inside the EHR where care is already happening — no extra tab to switch to, no broken flow, no alert fatigue.
</p>
</div>
<!-- Card 4 -->
<div class="bg-slate-50 p-8 rounded-2xl border border-slate-100 hover:shadow-xl transition-all group">
<div class="w-14 h-14 rounded-xl bg-brand-light text-brand flex items-center justify-center text-xl font-bold mb-6 group-hover:bg-brand group-hover:text-white transition-colors">
<i class="fa-solid fa-circle-nodes"></i>
</div>
<h4 class="text-xl font-bold text-slate-900 mb-3">FHIR® &amp; HL7v2 Native</h4>
<p class="text-slate-600 text-sm leading-relaxed">
Built on open healthcare standards, so guidance is generated from the complete clinical picture — labs, pharmacy, history, and beyond.
</p>
</div>
</div>
</div>
</section>
<!-- Interoperability Section -->
<section id="interoperability" class="py-24 bg-slate-900 text-white relative overflow-hidden">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center">
<div class="space-y-6">
<span class="text-brand-accent text-xs font-bold uppercase tracking-widest bg-brand-accent/10 px-3 py-1 rounded-full">Standards-Based Exchange</span>
<h2 class="text-3xl lg:text-4xl font-extrabold tracking-tight">Connected to the systems you already run</h2>
<p class="text-slate-300">
Guidance is only as strong as the data behind it. Plus Health Tech exchanges structured messaging with your EHR, labs, pharmacy, and registries through FHIR® and HL7v2 — so every recommendation reflects the complete, current clinical picture.
</p>
<ul class="space-y-3 text-sm text-slate-300">
<li class="flex items-center gap-3">
<i class="fa-solid fa-check text-brand-accent"></i> FHIR® R4 resources &amp; HL7v2 messaging
</li>
<li class="flex items-center gap-3">
<i class="fa-solid fa-check text-brand-accent"></i> Encrypted, enterprise-grade data exchange
</li>
<li class="flex items-center gap-3">
<i class="fa-solid fa-check text-brand-accent"></i> Real-time synchronization across care sites
</li>
</ul>
</div>
<div class="bg-slate-800/80 p-8 rounded-2xl border border-slate-700 backdrop-blur-sm">
<div class="flex items-center justify-between pb-3 border-b border-slate-700 text-xs text-slate-400">
<span>Message Exchange</span>
<span class="text-emerald-400 font-mono">ACK · 200 OK</span>
</div>
<div class="space-y-3">
<div class="font-mono text-xs bg-slate-950 p-4 rounded-xl overflow-x-auto">
<p class="text-slate-500 mb-1"><span class="text-emerald-400 font-bold">IN</span> · HL7v2 lab result</p>
<p class="text-brand-accent">OBR|1|ORD-48213|LAB-88910^LIS||Potassium|Stat</p>
</div>
<div class="font-mono text-xs bg-slate-950 p-4 rounded-xl overflow-x-auto">
<p class="text-slate-500 mb-1"><span class="text-sky-400 font-bold">OUT</span> · FHIR® CDS recommendation</p>
<p class="text-brand-accent">"code": "renal-dosing-review", "status": "active"</p>
</div>
</div>
<p class="pt-4 text-xs text-slate-400 text-center">Structured messaging in both directions — no point-to-point integrations to maintain.</p>
</div>
</div>
</div>
</section>
<!-- CTA Section -->
<section class="py-24 bg-slate-50">
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="bg-gradient-to-br from-brand to-brand-dark text-white rounded-2xl p-10 sm:p-14 shadow-xl shadow-brand/20 text-center space-y-4">
<h2 class="text-3xl font-extrabold tracking-tight">See CDS working in your workflow</h2>
<p class="text-slate-200 max-w-2xl mx-auto">
Book a walkthrough with our clinical team and watch live decision support run against the way your care setting actually operates.
</p>
<div class="flex flex-col sm:flex-row items-center justify-center gap-4 pt-2">
<a href="/RequestDemo" class="w-full sm:w-auto bg-white text-brand hover:bg-slate-100 font-semibold px-8 py-4 rounded-xl shadow-md transition-all text-center">
Request a demo <i class="fa-solid fa-arrow-right ml-2"></i>
</a>
<a href="/PatientManagement" class="w-full sm:w-auto text-white font-semibold px-8 py-4 rounded-xl border border-white/40 hover:bg-white/10 transition-all text-center">
Explore Patient Management
</a>
</div>
</div>
</div>
</section>
@@ -0,0 +1,19 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace plushealthtech.Pages
{
public class ClinicalDecisionSupportModel : PageModel
{
private readonly ILogger<ClinicalDecisionSupportModel> _logger;
public ClinicalDecisionSupportModel(ILogger<ClinicalDecisionSupportModel> logger)
{
_logger = logger;
}
public void OnGet()
{
}
}
}
+99
View File
@@ -0,0 +1,99 @@
@page
@model ContactModel
@{
ViewData["Title"] = "Contact";
}
<!-- Hero Section -->
<section class="relative overflow-hidden pt-16 pb-16 lg:pt-24 lg:pb-20 bg-gradient-to-b from-white via-slate-50 to-brand-light/30">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
<div class="max-w-3xl mx-auto space-y-4 text-center">
<span class="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-brand-light text-brand text-xs font-bold uppercase tracking-wider">
<i class="fa-solid fa-circle-nodes"></i> Get in Touch
</span>
<h1 class="text-4xl sm:text-5xl font-extrabold text-slate-900 tracking-tight leading-tight">
Contact <span class="text-[#1c8098]">Plus Health Tech</span>
</h1>
<p class="text-lg text-slate-600">
Questions about interoperability, product fit, or a live demo — send us a note and we'll get back to you.
</p>
</div>
</div>
</section>
<!-- Contact Form -->
<section class="pb-24 bg-slate-50">
<div class="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8">
@if (Model.Error != null)
{
<div class="mb-6 p-4 rounded-xl bg-red-50 border border-red-200 text-red-800 text-sm flex items-start gap-3">
<i class="fa-solid fa-circle-exclamation mt-0.5"></i>
<span>@Model.Error</span>
</div>
}
@if (Model.Success != null)
{
<div class="bg-white p-8 rounded-2xl border border-emerald-200 shadow-sm text-center space-y-4">
<div class="w-14 h-14 mx-auto rounded-full bg-emerald-100 text-emerald-600 flex items-center justify-center text-xl">
<i class="fa-solid fa-check"></i>
</div>
<h2 class="text-xl font-bold text-slate-900">Message sent</h2>
<p class="text-slate-600">@Model.Success</p>
<a href="/" class="inline-flex items-center gap-1 text-brand font-semibold text-sm hover:underline">
<i class="fa-solid fa-arrow-left text-xs"></i> Back to home
</a>
</div>
}
else
{
<div class="bg-white p-8 rounded-2xl border border-slate-100 shadow-sm">
<form asp-page="/Contact" method="post">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-5">
<div>
<label asp-for="Name" class="block text-sm font-semibold text-slate-700 mb-1.5">Name *</label>
<input asp-for="Name" class="w-full px-4 py-3 rounded-xl border border-slate-200 bg-white text-sm text-slate-800 placeholder:text-slate-400 focus:outline-none focus:border-brand focus:ring-2 focus:ring-brand/20" placeholder="Dr. Jane Smith" />
<span asp-validation-for="Name" class="mt-1 block text-xs text-red-600"></span>
</div>
<div>
<label asp-for="Email" class="block text-sm font-semibold text-slate-700 mb-1.5">Work email *</label>
<input asp-for="Email" type="email" class="w-full px-4 py-3 rounded-xl border border-slate-200 bg-white text-sm text-slate-800 placeholder:text-slate-400 focus:outline-none focus:border-brand focus:ring-2 focus:ring-brand/20" placeholder="[email protected]" />
<span asp-validation-for="Email" class="mt-1 block text-xs text-red-600"></span>
</div>
<div>
<label asp-for="Organization" class="block text-sm font-semibold text-slate-700 mb-1.5">Organization</label>
<input asp-for="Organization" class="w-full px-4 py-3 rounded-xl border border-slate-200 bg-white text-sm text-slate-800 placeholder:text-slate-400 focus:outline-none focus:border-brand focus:ring-2 focus:ring-brand/20" placeholder="Acme Health" />
<span asp-validation-for="Organization" class="mt-1 block text-xs text-red-600"></span>
</div>
<div>
<label asp-for="Phone" class="block text-sm font-semibold text-slate-700 mb-1.5">Phone</label>
<input asp-for="Phone" type="tel" class="w-full px-4 py-3 rounded-xl border border-slate-200 bg-white text-sm text-slate-800 placeholder:text-slate-400 focus:outline-none focus:border-brand focus:ring-2 focus:ring-brand/20" placeholder="+1 555 0100" />
<span asp-validation-for="Phone" class="mt-1 block text-xs text-red-600"></span>
</div>
</div>
<div class="mt-5">
<label asp-for="Message" class="block text-sm font-semibold text-slate-700 mb-1.5">Message *</label>
<textarea asp-for="Message" rows="6" class="w-full px-4 py-3 rounded-xl border border-slate-200 bg-white text-sm text-slate-800 placeholder:text-slate-400 focus:outline-none focus:border-brand focus:ring-2 focus:ring-brand/20" placeholder="Tell us what you're trying to solve…"></textarea>
<span asp-validation-for="Message" class="mt-1 block text-xs text-red-600"></span>
</div>
<div class="mt-6 flex flex-col sm:flex-row items-center gap-4">
<button type="submit" class="w-full sm:w-auto bg-[#1c8098] hover:bg-[#145f72] text-white font-semibold px-8 py-3.5 rounded-xl shadow-sm shadow-brand/20 transition-all">
Send message <i class="fa-solid fa-arrow-right ml-2"></i>
</button>
<p class="text-xs text-slate-500 flex items-center gap-2">
<i class="fa-solid fa-shield-halved text-brand"></i>
We reply to the email address you provide — nothing else is stored.
</p>
</div>
</form>
</div>
}
</div>
</section>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
+94
View File
@@ -0,0 +1,94 @@
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using plushealthtech.Mail;
namespace plushealthtech.Pages
{
public class ContactModel : PageModel
{
private readonly MailSender _mailSender;
private readonly ILogger<ContactModel> _logger;
public ContactModel(MailSender mailSender, ILogger<ContactModel> logger)
{
_mailSender = mailSender;
_logger = logger;
}
[BindProperty]
[Required]
[StringLength(200)]
public string Name { get; set; } = string.Empty;
[BindProperty]
[Required]
[EmailAddress]
[StringLength(320)]
public string Email { get; set; } = string.Empty;
[BindProperty]
[StringLength(200)]
public string? Organization { get; set; }
[BindProperty]
[StringLength(50)]
public string? Phone { get; set; }
[BindProperty]
[Required]
[StringLength(5000)]
public string Message { get; set; } = string.Empty;
public string? Success { get; set; }
public string? Error { get; set; }
public void OnGet()
{
Success = null;
Error = null;
}
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
var lines = new List<string>
{
$"Name: {Name}",
$"Email: {Email}"
};
if (!string.IsNullOrWhiteSpace(Organization))
{
lines.Add($"Organization: {Organization}");
}
if (!string.IsNullOrWhiteSpace(Phone))
{
lines.Add($"Phone: {Phone}");
}
lines.Add(string.Empty);
lines.Add(Message);
try
{
await _mailSender.SendAsync($"New contact form message — {Name}", string.Join("\n", lines));
Success = $"Thanks — your message has been sent. We'll reply to {Email}.";
}
catch (Exception ex)
{
// Log the full exception; never surface SMTP details to the user.
_logger.LogError(ex, "Failed to send contact form message from {Email}", Email);
Error = "Your message could not be sent right now. Please try again or email us directly.";
}
return Page();
}
}
}
+4 -4
View File
@@ -20,7 +20,7 @@
Deliver superior healthcare outcomes with our fully interoperable Clinical Decision Support (CDS), advanced Patient Management software, and specialized veterinary platforms.
</p>
<div class="flex flex-col sm:flex-row items-center justify-center lg:justify-start gap-4 pt-4">
<a href="#contact" class="w-full sm:w-auto bg-[#1c8098] hover:bg-[#145f72] text-white font-semibold px-8 py-4 rounded-xl shadow-lg shadow-brand/25 transition-all text-center">
<a href="/RequestDemo" class="w-full sm:w-auto bg-[#1c8098] hover:bg-[#145f72] text-white font-semibold px-8 py-4 rounded-xl shadow-lg shadow-brand/25 transition-all text-center">
Explore Platform <i class="fa-solid fa-arrow-right ml-2"></i>
</a>
<a href="#solutions" class="w-full sm:w-auto bg-white hover:bg-slate-50 text-slate-700 font-semibold px-8 py-4 rounded-xl border border-slate-200 transition-all text-center">
@@ -101,7 +101,7 @@
<p class="text-slate-600 text-sm leading-relaxed mb-6">
Deliver evidence-based guidance at the point of care with intelligent algorithms built to reduce medical errors and optimize treatment choices.
</p>
<a href="#" class="text-brand font-semibold text-sm inline-flex items-center gap-1 hover:gap-2 transition-all">
<a href="/ClinicalDecisionSupport" class="text-brand font-semibold text-sm inline-flex items-center gap-1 hover:gap-2 transition-all">
Learn more <i class="fa-solid fa-arrow-right text-xs"></i>
</a>
</div>
@@ -115,7 +115,7 @@
<p class="text-slate-600 text-sm leading-relaxed mb-6">
End-to-end patient workflow tools from intake to discharge, maximizing clinic efficiency and keeping communication channels transparent.
</p>
<a href="#" class="text-brand font-semibold text-sm inline-flex items-center gap-1 hover:gap-2 transition-all">
<a href="/PatientManagement" class="text-brand font-semibold text-sm inline-flex items-center gap-1 hover:gap-2 transition-all">
Learn more <i class="fa-solid fa-arrow-right text-xs"></i>
</a>
</div>
@@ -129,7 +129,7 @@
<p class="text-slate-600 text-sm leading-relaxed mb-6">
Specialized veterinary software tailored for animal clinics, integrating EHR records, imaging, and tailored diagnostic decision tools.
</p>
<a href="#" class="text-brand-vet font-semibold text-sm inline-flex items-center gap-1 hover:gap-2 transition-all">
<a href="/Veterinary" class="text-brand-vet font-semibold text-sm inline-flex items-center gap-1 hover:gap-2 transition-all">
Explore Vet Tech <i class="fa-solid fa-arrow-right text-xs"></i>
</a>
</div>
@@ -0,0 +1,107 @@
@page
@model PatientManagementModel
@{
ViewData["Title"] = "Patient Management";
}
<!-- Hero Section -->
<section class="relative overflow-hidden pt-16 pb-20 lg:pt-24 lg:pb-24 bg-gradient-to-b from-white via-slate-50 to-brand-light/30">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
<div class="max-w-3xl mx-auto space-y-4 text-center">
<span class="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-brand-light text-brand text-xs font-bold uppercase tracking-wider">
<i class="fa-solid fa-hospital-user"></i> Patient-Centered Workflow
</span>
<h1 class="text-4xl sm:text-5xl font-extrabold text-slate-900 tracking-tight leading-tight">
End-to-end patient workflow, <span class="text-brand">from intake to discharge</span>
</h1>
<p class="text-lg text-slate-600">
One connected platform for registration, scheduling, and every touchpoint in between — so your team maximizes clinic efficiency and keeps communication channels transparent.
</p>
<div class="flex flex-col sm:flex-row items-center justify-center gap-4 pt-4">
<a href="/RequestDemo" class="w-full sm:w-auto bg-brand hover:bg-brand-dark text-white font-semibold px-8 py-4 rounded-xl shadow-lg shadow-brand/25 transition-all text-center">
Request a demo <i class="fa-solid fa-arrow-right ml-2"></i>
</a>
<a href="/ClinicalDecisionSupport" class="w-full sm:w-auto bg-white hover:bg-slate-50 text-slate-700 font-semibold px-8 py-4 rounded-xl border border-slate-200 transition-all text-center">
Explore Clinical Decision Support
</a>
</div>
</div>
</div>
</section>
<!-- Features Section -->
<section id="features" class="py-24 bg-white">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="text-center max-w-3xl mx-auto space-y-4 mb-16">
<h2 class="text-xs font-bold tracking-widest text-brand uppercase">Patient Management</h2>
<h3 class="text-3xl font-extrabold text-slate-900 tracking-tight">Every step of the patient journey, one connected workflow</h3>
<p class="text-slate-600">From the front desk to final follow-up — purpose-built tools that keep intake, scheduling, communication, and clinical handoffs moving smoothly.</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
<!-- Card 1 -->
<div class="bg-slate-50 p-8 rounded-2xl border border-slate-100 hover:shadow-xl transition-all group">
<div class="w-14 h-14 rounded-xl bg-brand-light text-brand flex items-center justify-center text-xl font-bold mb-6 group-hover:bg-brand group-hover:text-white transition-colors">
<i class="fa-solid fa-clipboard-user"></i>
</div>
<h4 class="text-xl font-bold text-slate-900 mb-3">Intake &amp; Registration</h4>
<p class="text-slate-600 text-sm leading-relaxed">
Capture demographics, insurance, and medical history once — then carry it across every visit, so check-in is fast and the chart is complete from the first minute.
</p>
</div>
<!-- Card 2 -->
<div class="bg-slate-50 p-8 rounded-2xl border border-slate-100 hover:shadow-xl transition-all group">
<div class="w-14 h-14 rounded-xl bg-brand-light text-brand flex items-center justify-center text-xl font-bold mb-6 group-hover:bg-brand group-hover:text-white transition-colors">
<i class="fa-solid fa-calendar-check"></i>
</div>
<h4 class="text-xl font-bold text-slate-900 mb-3">Scheduling &amp; Capacity</h4>
<p class="text-slate-600 text-sm leading-relaxed">
Balance provider calendars, room availability, and waitlists in one view — reducing no-shows and keeping appointment flow steady through the busiest days.
</p>
</div>
<!-- Card 3 -->
<div class="bg-slate-50 p-8 rounded-2xl border border-slate-100 hover:shadow-xl transition-all group">
<div class="w-14 h-14 rounded-xl bg-brand-light text-brand flex items-center justify-center text-xl font-bold mb-6 group-hover:bg-brand group-hover:text-white transition-colors">
<i class="fa-solid fa-comment-medical"></i>
</div>
<h4 class="text-xl font-bold text-slate-900 mb-3">Transparent Patient Communication</h4>
<p class="text-slate-600 text-sm leading-relaxed">
Keep patients informed through the channels they already use — reminders, results, and status updates delivered clearly, so unanswered questions never stall care.
</p>
</div>
<!-- Card 4 -->
<div class="bg-slate-50 p-8 rounded-2xl border border-slate-100 hover:shadow-xl transition-all group">
<div class="w-14 h-14 rounded-xl bg-brand-light text-brand flex items-center justify-center text-xl font-bold mb-6 group-hover:bg-brand group-hover:text-white transition-colors">
<i class="fa-solid fa-circle-nodes"></i>
</div>
<h4 class="text-xl font-bold text-slate-900 mb-3">EHR Integration, FHIR®-Ready</h4>
<p class="text-slate-600 text-sm leading-relaxed">
Connects to the EHRs and systems you already run through FHIR®-ready exchange — no rip-and-replace, no parallel charts, no data silos.
</p>
</div>
</div>
</div>
</section>
<!-- CTA Section -->
<section class="py-24 bg-slate-50">
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="bg-gradient-to-br from-brand to-brand-dark text-white rounded-2xl p-10 sm:p-14 shadow-xl shadow-brand/20 text-center space-y-4">
<h2 class="text-3xl font-extrabold tracking-tight">See patient management working in your workflow</h2>
<p class="text-slate-200 max-w-2xl mx-auto">
Book a walkthrough with our team and watch the full intake-to-discharge flow run against the way your practice actually operates.
</p>
<div class="flex flex-col sm:flex-row items-center justify-center gap-4 pt-2">
<a href="/RequestDemo" class="w-full sm:w-auto bg-white text-brand hover:bg-slate-100 font-semibold px-8 py-4 rounded-xl shadow-md transition-all text-center">
Request a demo <i class="fa-solid fa-arrow-right ml-2"></i>
</a>
<a href="/ClinicalDecisionSupport" class="w-full sm:w-auto text-white font-semibold px-8 py-4 rounded-xl border border-white/40 hover:bg-white/10 transition-all text-center">
Explore Clinical Decision Support
</a>
</div>
</div>
</div>
</section>
@@ -0,0 +1,19 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace plushealthtech.Pages
{
public class PatientManagementModel : PageModel
{
private readonly ILogger<PatientManagementModel> _logger;
public PatientManagementModel(ILogger<PatientManagementModel> logger)
{
_logger = logger;
}
public void OnGet()
{
}
}
}
+116 -3
View File
@@ -1,8 +1,121 @@
@page
@page
@model PrivacyModel
@{
ViewData["Title"] = "Privacy Policy";
}
<h1>@ViewData["Title"]</h1>
<p>Use this page to detail your site's privacy policy.</p>
<!-- Hero Section -->
<section class="relative overflow-hidden pt-16 pb-16 lg:pt-24 lg:pb-20 bg-gradient-to-b from-white via-slate-50 to-brand-light/30">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
<div class="max-w-3xl mx-auto space-y-4 text-center">
<span class="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-brand-light text-brand text-xs font-bold uppercase tracking-wider">
<i class="fa-solid fa-shield-halved"></i> Privacy
</span>
<h1 class="text-4xl sm:text-5xl font-extrabold text-slate-900 tracking-tight leading-tight">
Privacy, <span class="text-[#1c8098]">handled with care</span>
</h1>
<p class="text-lg text-slate-600">
We keep this site deliberately simple, and our data collection with it. Here's exactly what we collect when you get in touch, what we do with it, and what you can ask us to do.
</p>
</div>
</div>
</section>
<!-- Policy -->
<section class="pb-24 bg-slate-50">
<div class="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="bg-white p-8 sm:p-10 rounded-2xl border border-slate-100 shadow-sm">
<!-- Information we collect -->
<div>
<h2 class="text-lg font-bold text-slate-900 mb-2">Information we collect</h2>
<p class="text-sm leading-relaxed text-slate-600">
Most of this site is read-only — you can browse it without giving us anything. When you send a message through the Contact page or request a demo, we collect only what those forms ask for: your name, work email, and organization; your phone number on the Contact form; and, on the demo form, your practice type, the products you're interested in, and any additional notes. Fields marked <span class="font-semibold text-slate-800">*</span> are the only ones we require. We don't collect patient or animal health information here — this is a company website, not a clinical system.
</p>
</div>
<hr class="my-8 border-slate-100" />
<!-- How we use it -->
<div>
<h2 class="text-lg font-bold text-slate-900 mb-2">How we use it</h2>
<p class="text-sm leading-relaxed text-slate-600">
Your details are used for one purpose: to respond to you. Contact messages and demo requests are delivered to our team mailbox (<span class="font-mono text-xs text-slate-700">[email protected]</span>), and we reply to the email address you provided. We don't sell or rent your information, we don't share it with third parties for their marketing, and we don't use it to build profiles or serve you advertising.
</p>
</div>
<hr class="my-8 border-slate-100" />
<!-- Third-party services -->
<div>
<h2 class="text-lg font-bold text-slate-900 mb-2">Third-party services</h2>
<p class="text-sm leading-relaxed text-slate-600 mb-3">
The site doesn't run much third-party machinery, but a few services help it work:
</p>
<ul class="space-y-2">
<li class="text-sm leading-relaxed text-slate-600">
<span class="font-semibold text-slate-800">Font Awesome (via cdnjs)</span> — the icon set is delivered as a stylesheet from the cdnjs content-delivery network.
</li>
<li class="text-sm leading-relaxed text-slate-600">
<span class="font-semibold text-slate-800">Analytics (analytics.rokoh.com)</span> — a lightweight script that measures aggregate page views across the site.
</li>
<li class="text-sm leading-relaxed text-slate-600">
<span class="font-semibold text-slate-800">Blog (blog.plushealthtech.com)</span> — our blog lives on a separate subdomain; visiting it is subject to whatever terms are stated there.
</li>
</ul>
</div>
<hr class="my-8 border-slate-100" />
<!-- Cookies -->
<div>
<h2 class="text-lg font-bold text-slate-900 mb-2">Cookies</h2>
<p class="text-sm leading-relaxed text-slate-600">
This site sets no session or authentication cookies — there's nothing to log in to. The only cookies you may encounter are the analytics cookies set by the analytics script above, which track aggregate usage rather than building an individual profile. Blocking third-party cookies in your browser will not affect the site's functionality.
</p>
</div>
<hr class="my-8 border-slate-100" />
<!-- Data retention -->
<div>
<h2 class="text-lg font-bold text-slate-900 mb-2">Data retention</h2>
<p class="text-sm leading-relaxed text-slate-600">
Contact and demo messages are kept in our team mailbox while we work on them and for a period afterwards. The exact retention schedule is still being finalized — this section is a placeholder pending legal review and will be updated once our schedule is set.
</p>
<div class="mt-4 p-4 rounded-xl bg-amber-50 border border-amber-200 text-amber-800 text-xs flex items-start gap-3">
<i class="fa-solid fa-triangle-exclamation mt-0.5"></i>
<span>Pending legal review — the wording in this section is provisional.</span>
</div>
</div>
<hr class="my-8 border-slate-100" />
<!-- Your rights -->
<div>
<h2 class="text-lg font-bold text-slate-900 mb-2">Your rights</h2>
<p class="text-sm leading-relaxed text-slate-600">
Under applicable data-protection laws you can ask us to confirm what we hold about you, correct inaccurate details, or delete your information. Send any such request through the <a href="/Contact" class="text-brand font-semibold hover:underline">Contact page</a> and we'll take care of it, responding to the email address you provide.
</p>
</div>
<hr class="my-8 border-slate-100" />
<!-- Contact -->
<div>
<h2 class="text-lg font-bold text-slate-900 mb-2">Contact</h2>
<p class="text-sm leading-relaxed text-slate-600">
Questions about this policy, or a request about your data? The fastest route to a human is the <a href="/Contact" class="text-brand font-semibold hover:underline">Contact page</a> — or request a demo and tell us what you'd like to know.
</p>
</div>
<!-- Last updated -->
<div class="mt-10 pt-6 border-t border-slate-100">
<p class="text-xs text-slate-500">
Last updated: August 2026
</p>
</div>
</div>
</div>
</section>
+122
View File
@@ -0,0 +1,122 @@
@page
@model RequestDemoModel
@{
ViewData["Title"] = "Request Demo";
}
<!-- Hero Section -->
<section class="relative overflow-hidden pt-16 pb-16 lg:pt-24 lg:pb-20 bg-gradient-to-b from-white via-slate-50 to-brand-light/30">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
<div class="max-w-3xl mx-auto space-y-4 text-center">
<span class="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-brand-light text-brand text-xs font-bold uppercase tracking-wider">
<i class="fa-solid fa-circle-nodes"></i> See It In Action
</span>
<h1 class="text-4xl sm:text-5xl font-extrabold text-slate-900 tracking-tight leading-tight">
Request a <span class="text-[#1c8098]">Live Demo</span>
</h1>
<p class="text-lg text-slate-600">
See how interoperable Clinical Decision Support, Patient Management, and Veterinary platforms fit your practice — in a walkthrough tailored to your team.
</p>
<div class="flex flex-wrap items-center justify-center gap-x-6 gap-y-2 pt-2 text-sm text-slate-600 font-medium">
<span class="inline-flex items-center gap-2"><i class="fa-solid fa-circle-check text-brand"></i> FHIR®-ready interoperability</span>
<span class="inline-flex items-center gap-2"><i class="fa-solid fa-circle-check text-brand"></i> 24/7 clinical support</span>
<span class="inline-flex items-center gap-2"><i class="fa-solid fa-circle-check text-brand"></i> 99.9% system uptime</span>
</div>
</div>
</div>
</section>
<!-- Request Demo Form -->
<section class="pb-24 bg-slate-50">
<div class="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8">
@if (Model.Error != null)
{
<div class="mb-6 p-4 rounded-xl bg-red-50 border border-red-200 text-red-800 text-sm flex items-start gap-3">
<i class="fa-solid fa-circle-exclamation mt-0.5"></i>
<span>@Model.Error</span>
</div>
}
@if (Model.Success != null)
{
<div class="bg-white p-8 rounded-2xl border border-emerald-200 shadow-sm text-center space-y-4">
<div class="w-14 h-14 mx-auto rounded-full bg-emerald-100 text-emerald-600 flex items-center justify-center text-xl">
<i class="fa-solid fa-check"></i>
</div>
<h2 class="text-xl font-bold text-slate-900">Demo request sent</h2>
<p class="text-slate-600">@Model.Success</p>
<div class="flex flex-col sm:flex-row items-center justify-center gap-4 pt-2">
<a href="/" class="inline-flex items-center gap-1 text-brand font-semibold text-sm hover:underline">
<i class="fa-solid fa-arrow-left text-xs"></i> Back to home
</a>
<a href="/Contact" class="inline-flex items-center gap-1 text-slate-600 font-semibold text-sm hover:text-brand hover:underline">
Prefer to talk? Visit the Contact page <i class="fa-solid fa-arrow-right text-xs"></i>
</a>
</div>
</div>
}
else
{
<div class="bg-white p-8 rounded-2xl border border-slate-100 shadow-sm">
<form asp-page="/RequestDemo" method="post">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-5">
<div>
<label asp-for="Name" class="block text-sm font-semibold text-slate-700 mb-1.5">Full name *</label>
<input asp-for="Name" class="w-full px-4 py-3 rounded-xl border border-slate-200 bg-white text-sm text-slate-800 placeholder:text-slate-400 focus:outline-none focus:border-brand focus:ring-2 focus:ring-brand/20" placeholder="Dr. Jane Smith" />
<span asp-validation-for="Name" class="mt-1 block text-xs text-red-600"></span>
</div>
<div>
<label asp-for="Email" class="block text-sm font-semibold text-slate-700 mb-1.5">Work email *</label>
<input asp-for="Email" type="email" class="w-full px-4 py-3 rounded-xl border border-slate-200 bg-white text-sm text-slate-800 placeholder:text-slate-400 focus:outline-none focus:border-brand focus:ring-2 focus:ring-brand/20" placeholder="[email protected]" />
<span asp-validation-for="Email" class="mt-1 block text-xs text-red-600"></span>
</div>
<div>
<label asp-for="Organization" class="block text-sm font-semibold text-slate-700 mb-1.5">Organization *</label>
<input asp-for="Organization" class="w-full px-4 py-3 rounded-xl border border-slate-200 bg-white text-sm text-slate-800 placeholder:text-slate-400 focus:outline-none focus:border-brand focus:ring-2 focus:ring-brand/20" placeholder="Acme Health" />
<span asp-validation-for="Organization" class="mt-1 block text-xs text-red-600"></span>
</div>
<div>
<label asp-for="PracticeType" class="block text-sm font-semibold text-slate-700 mb-1.5">Practice type</label>
<select asp-for="PracticeType" asp-items="Model.PracticeTypeOptions" class="w-full px-4 py-3 rounded-xl border border-slate-200 bg-white text-sm text-slate-800 focus:outline-none focus:border-brand focus:ring-2 focus:ring-brand/20"></select>
<span asp-validation-for="PracticeType" class="mt-1 block text-xs text-red-600"></span>
</div>
<div>
<label asp-for="ProductsOfInterest" class="block text-sm font-semibold text-slate-700 mb-1.5">Products of interest</label>
<select asp-for="ProductsOfInterest" asp-items="Model.ProductsOfInterestOptions" class="w-full px-4 py-3 rounded-xl border border-slate-200 bg-white text-sm text-slate-800 focus:outline-none focus:border-brand focus:ring-2 focus:ring-brand/20"></select>
<span asp-validation-for="ProductsOfInterest" class="mt-1 block text-xs text-red-600"></span>
</div>
</div>
<div class="mt-5">
<label asp-for="Message" class="block text-sm font-semibold text-slate-700 mb-1.5">Anything you'd like us to know?</label>
<textarea asp-for="Message" rows="5" class="w-full px-4 py-3 rounded-xl border border-slate-200 bg-white text-sm text-slate-800 placeholder:text-slate-400 focus:outline-none focus:border-brand focus:ring-2 focus:ring-brand/20" placeholder="Tell us about your setup, go-live timeline, or anything you'd like to see…"></textarea>
<span asp-validation-for="Message" class="mt-1 block text-xs text-red-600"></span>
</div>
<div class="mt-6 flex flex-col sm:flex-row items-center gap-4">
<button type="submit" class="w-full sm:w-auto bg-[#1c8098] hover:bg-[#145f72] text-white font-semibold px-8 py-3.5 rounded-xl shadow-sm shadow-brand/20 transition-all">
Request demo <i class="fa-solid fa-arrow-right ml-2"></i>
</button>
<p class="text-xs text-slate-500 flex items-center gap-2">
<i class="fa-solid fa-shield-halved text-brand"></i>
We'll only use your details to schedule your demo.
</p>
</div>
</form>
</div>
}
<!-- Secondary CTA -->
<div class="mt-8 text-center">
<p class="text-sm text-slate-500">
Prefer to talk?
<a href="/Contact" class="text-brand font-semibold hover:underline">Visit the Contact page</a>
<i class="fa-solid fa-arrow-right text-xs inline-block"></i>
</p>
</div>
</div>
</section>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
+120
View File
@@ -0,0 +1,120 @@
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using plushealthtech.Mail;
namespace plushealthtech.Pages
{
public class RequestDemoModel : PageModel
{
private readonly MailSender _mailSender;
private readonly ILogger<RequestDemoModel> _logger;
public RequestDemoModel(MailSender mailSender, ILogger<RequestDemoModel> logger)
{
_mailSender = mailSender;
_logger = logger;
}
[BindProperty]
[Required]
[StringLength(200)]
public string Name { get; set; } = string.Empty;
[BindProperty]
[Required]
[EmailAddress]
[StringLength(320)]
public string Email { get; set; } = string.Empty;
[BindProperty]
[Required]
[StringLength(200)]
public string Organization { get; set; } = string.Empty;
[BindProperty]
public string? PracticeType { get; set; }
[BindProperty]
public string? ProductsOfInterest { get; set; }
[BindProperty]
[StringLength(5000)]
public string? Message { get; set; }
public List<SelectListItem> PracticeTypeOptions { get; } = new()
{
new SelectListItem { Value = string.Empty, Text = "Select a practice type…", Selected = true },
new SelectListItem { Value = "Healthcare system", Text = "Healthcare system" },
new SelectListItem { Value = "Hospital", Text = "Hospital" },
new SelectListItem { Value = "Clinic", Text = "Clinic" },
new SelectListItem { Value = "Veterinary practice", Text = "Veterinary practice" },
new SelectListItem { Value = "Other", Text = "Other" },
};
public List<SelectListItem> ProductsOfInterestOptions { get; } = new()
{
new SelectListItem { Value = string.Empty, Text = "Select a product area…", Selected = true },
new SelectListItem { Value = "Clinical Decision Support", Text = "Clinical Decision Support" },
new SelectListItem { Value = "Patient Management", Text = "Patient Management" },
new SelectListItem { Value = "Veterinary Solutions", Text = "Veterinary Solutions" },
new SelectListItem { Value = "Multiple / not sure", Text = "Multiple / not sure" },
};
public string? Success { get; set; }
public string? Error { get; set; }
public void OnGet()
{
Success = null;
Error = null;
}
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
var lines = new List<string>
{
$"Name: {Name}",
$"Email: {Email}",
$"Organization: {Organization}"
};
if (!string.IsNullOrWhiteSpace(PracticeType))
{
lines.Add($"Practice type: {PracticeType}");
}
if (!string.IsNullOrWhiteSpace(ProductsOfInterest))
{
lines.Add($"Products of interest: {ProductsOfInterest}");
}
if (!string.IsNullOrWhiteSpace(Message))
{
lines.Add(string.Empty);
lines.Add(Message);
}
try
{
await _mailSender.SendAsync($"New demo request — {Organization}", string.Join("\n", lines));
Success = $"Thanks — your demo request has been sent. We'll reach out to {Email}.";
}
catch (Exception ex)
{
// Log the full exception; never surface SMTP details to the user.
_logger.LogError(ex, "Failed to send demo request from {Email} ({Organization})", Email, Organization);
Error = "Your request could not be sent right now. Please try again or email us directly.";
}
return Page();
}
}
}
+72 -35
View File
@@ -4,25 +4,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - Plus Health Tech | Interoperable Clinical Decision Support & Patient Management</title>
<!-- Tailwind CSS CDN -->
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
brand: {
DEFAULT: '#1c8098',
dark: '#145f72',
light: '#e6f2f5',
accent: '#0ea5e9',
vet: '#0d9488'
}
}
}
}
}
</script>
<link rel="stylesheet" href="~/css/tailwind.css" />
<!-- FontAwesome for Icons -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<link rel="icon" type="image/x-icon" href="~/img/favicon.ico">
@@ -34,39 +16,58 @@
<header class="sticky top-0 z-50 bg-white/90 backdrop-blur-md border-b border-slate-100 shadow-xs">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-20 flex items-center justify-between">
<!-- Logo -->
<a href="#" class="flex items-center gap-3">
<a href="/" class="flex items-center gap-3">
<img src="https://www.plushealthtech.com/img/plushealthtech-logo.png" alt="Plus Health Tech Logo" class="h-10 w-auto object-contain">
</a>
<!-- Desktop Nav Links -->
<nav class="hidden md:flex items-center gap-8 font-medium text-sm text-slate-600">
<a href="#solutions" class="hover:text-brand transition-colors">Solutions</a>
<a href="#interoperability" class="hover:text-brand transition-colors">Interoperability</a>
<a href="#veterinary" class="hover:text-brand transition-colors">Veterinary Tech</a>
<a href="#about" class="hover:text-brand transition-colors">About Us</a>
<a href="/ClinicalDecisionSupport" class="hover:text-brand transition-colors">CDS</a>
<a href="/PatientManagement" class="hover:text-brand transition-colors">Patient Management</a>
<a href="/Veterinary" class="hover:text-brand transition-colors">Veterinary</a>
<a href="/About" class="hover:text-brand transition-colors">About</a>
<a href="/Careers" class="hover:text-brand transition-colors">Careers</a>
<a href="/Contact" class="hover:text-brand transition-colors">Contact</a>
<a href="https://blog.plushealthtech.com" class="hover:text-brand transition-colors">Blog</a>
</nav>
<!-- Action Buttons -->
<div class="hidden md:flex items-center gap-4">
<a href="#contact" class="text-sm font-semibold text-slate-700 hover:text-brand px-4 py-2">Sign In</a>
<a href="#contact" class="bg-[#1c8098] hover:bg-[#145f72] text-white text-sm font-semibold px-5 py-2.5 rounded-xl shadow-sm transition-all shadow-brand/20">
<a href="/SignIn" class="text-sm font-semibold text-slate-700 hover:text-brand px-4 py-2">Sign In</a>
<a href="/RequestDemo" class="bg-[#1c8098] hover:bg-[#145f72] text-white text-sm font-semibold px-5 py-2.5 rounded-xl shadow-sm transition-all shadow-brand/20">
Request Demo
</a>
</div>
<!-- Mobile Menu Toggle -->
<button class="md:hidden text-slate-600 focus:outline-none">
<button id="mobile-menu-toggle" class="md:hidden text-slate-600 focus:outline-none" aria-controls="mobile-menu" aria-expanded="false">
<i class="fa-solid fa-bars text-xl"></i>
</button>
</div>
<!-- Mobile Menu -->
<div id="mobile-menu" class="hidden md:hidden border-t border-slate-100 bg-white px-4 sm:px-6 pt-2 pb-6">
<nav class="flex flex-col font-medium text-sm text-slate-600">
<a href="/ClinicalDecisionSupport" class="py-2 hover:text-brand transition-colors">CDS</a>
<a href="/PatientManagement" class="py-2 hover:text-brand transition-colors">Patient Management</a>
<a href="/Veterinary" class="py-2 hover:text-brand transition-colors">Veterinary</a>
<a href="/About" class="py-2 hover:text-brand transition-colors">About</a>
<a href="/Careers" class="py-2 hover:text-brand transition-colors">Careers</a>
<a href="/Contact" class="py-2 hover:text-brand transition-colors">Contact</a>
<a href="https://blog.plushealthtech.com" class="py-2 hover:text-brand transition-colors">Blog</a>
</nav>
<div class="mt-4 flex flex-col gap-2">
<a href="/SignIn" class="text-sm font-semibold text-slate-700 hover:text-brand px-4 py-2.5 text-center border border-slate-200 rounded-xl transition-colors">Sign In</a>
<a href="/RequestDemo" class="bg-[#1c8098] hover:bg-[#145f72] text-white text-sm font-semibold px-5 py-2.5 rounded-xl text-center shadow-sm shadow-brand/20 transition-colors">Request Demo</a>
</div>
</div>
</header>
@RenderBody()
<!-- Footer -->
<footer id="contact" class="bg-slate-950 text-slate-400 py-16 border-t border-slate-900">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 grid grid-cols-1 md:grid-cols-4 gap-10">
<footer class="bg-slate-950 text-slate-400 py-16 border-t border-slate-900">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 grid grid-cols-1 md:grid-cols-5 gap-10">
<div class="space-y-4">
<img src="~/img/plushealthtech-logo.png" alt="Plus Health Tech" class="h-8 w-auto brightness-200">
<p class="text-xs text-slate-500">
@@ -76,17 +77,24 @@
<div>
<h5 class="text-white text-sm font-semibold mb-4">Solutions</h5>
<ul class="space-y-2 text-xs">
<li><a href="#" class="hover:text-white transition-colors">Clinical Decision Support</a></li>
<li><a href="#" class="hover:text-white transition-colors">Patient Management</a></li>
<li><a href="#" class="v-tech hover:text-white transition-colors">Veterinarian Tech</a></li>
<li><a href="/ClinicalDecisionSupport" class="hover:text-white transition-colors">Clinical Decision Support</a></li>
<li><a href="/PatientManagement" class="hover:text-white transition-colors">Patient Management</a></li>
<li><a href="/Veterinary" class="hover:text-white transition-colors">Veterinarian Solutions</a></li>
</ul>
</div>
<div>
<h5 class="text-white text-sm font-semibold mb-4">Company</h5>
<ul class="space-y-2 text-xs">
<li><a href="#" class="hover:text-white transition-colors">About Us</a></li>
<li><a href="#" class="hover:text-white transition-colors">Careers</a></li>
<li><a href="#" class="hover:text-white transition-colors">Contact</a></li>
<li><a href="/About" class="hover:text-white transition-colors">About Us</a></li>
<li><a href="/Careers" class="hover:text-white transition-colors">Careers</a></li>
<li><a href="/Contact" class="hover:text-white transition-colors">Contact</a></li>
</ul>
</div>
<div>
<h5 class="text-white text-sm font-semibold mb-4">Legal</h5>
<ul class="space-y-2 text-xs">
<li><a href="/Privacy" class="hover:text-white transition-colors">Privacy Policy</a></li>
<li><a href="/Terms" class="hover:text-white transition-colors">Terms &amp; Conditions</a></li>
</ul>
</div>
<div>
@@ -103,6 +111,35 @@
</div>
</footer>
<script>
(function () {
var toggle = document.getElementById('mobile-menu-toggle');
var menu = document.getElementById('mobile-menu');
if (!toggle || !menu) return;
var icon = toggle.querySelector('i');
function setIcon(open) {
icon.classList.toggle('fa-bars', !open);
icon.classList.toggle('fa-xmark', open);
}
toggle.addEventListener('click', function () {
var open = menu.classList.toggle('hidden') === false;
setIcon(open);
toggle.setAttribute('aria-expanded', open ? 'true' : 'false');
});
// Close the menu when a link is tapped
menu.querySelectorAll('a').forEach(function (link) {
link.addEventListener('click', function () {
menu.classList.add('hidden');
setIcon(false);
toggle.setAttribute('aria-expanded', 'false');
});
});
})();
</script>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>
+30
View File
@@ -0,0 +1,30 @@
@page
@model SignInModel
@{
ViewData["Title"] = "Sign In";
}
<!-- Sign In Placeholder -->
<section class="relative overflow-hidden pt-16 pb-24 lg:pt-24 lg:pb-32 bg-gradient-to-b from-white via-slate-50 to-brand-light/30">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
<div class="max-w-md mx-auto">
<div class="bg-white p-8 rounded-2xl border border-slate-100 shadow-sm text-center space-y-5">
<div class="w-14 h-14 mx-auto rounded-full bg-brand-light text-brand flex items-center justify-center text-xl">
<i class="fa-solid fa-right-to-bracket"></i>
</div>
<div>
<h1 class="text-2xl font-bold text-slate-900">Sign in to your portal</h1>
<p class="mt-2 text-sm text-slate-600">Portal sign-in is coming soon.</p>
</div>
<div class="flex flex-col gap-3 pt-1">
<a href="/RequestDemo" class="inline-flex items-center justify-center gap-2 bg-[#1c8098] hover:bg-[#145f72] text-white font-semibold px-8 py-3.5 rounded-xl shadow-sm shadow-brand/20 transition-all">
Request a demo <i class="fa-solid fa-arrow-right"></i>
</a>
<a href="/Contact" class="inline-flex items-center justify-center gap-2 text-slate-600 hover:text-brand font-semibold px-8 py-3.5 rounded-xl border border-slate-200 hover:border-brand/40 transition-all">
Contact us instead
</a>
</div>
</div>
</div>
</div>
</section>
+11
View File
@@ -0,0 +1,11 @@
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace plushealthtech.Pages
{
public class SignInModel : PageModel
{
public void OnGet()
{
}
}
}
+228
View File
@@ -0,0 +1,228 @@
@page
@model TermsModel
@{
ViewData["Title"] = "Terms & Conditions";
}
<!--
NOTE (legal): Standard corporate boilerplate — flagged for legal review before production
(see plan §3.6 and §8). Placeholders such as the governing jurisdiction below are
intentionally not finalized. Nothing here is legal advice.
-->
<!-- Hero Section -->
<section class="relative overflow-hidden pt-16 pb-16 lg:pt-24 lg:pb-20 bg-gradient-to-b from-white via-slate-50 to-brand-light/30">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
<div class="max-w-3xl mx-auto space-y-4 text-center">
<span class="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-brand-light text-brand text-xs font-bold uppercase tracking-wider">
<i class="fa-solid fa-scale-balanced"></i> Legal
</span>
<h1 class="text-4xl sm:text-5xl font-extrabold text-slate-900 tracking-tight leading-tight">
Terms <span class="text-brand">&amp; Conditions</span>
</h1>
<p class="text-lg text-slate-600">
The terms that govern your use of the Plus Health Tech website and the services it offers.
</p>
<p class="text-xs text-slate-500 inline-flex items-center gap-2">
<i class="fa-regular fa-calendar"></i> Last updated: 2026
</p>
</div>
</div>
</section>
<!-- Terms Body -->
<section class="pb-24 bg-slate-50">
<div class="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8">
<article class="bg-white rounded-2xl border border-slate-100 shadow-sm p-8 sm:p-12 space-y-10">
<p class="text-slate-600 leading-relaxed">
These Terms &amp; Conditions ("Terms") set out the rules for using the plushealthtech.com website
(the "Site"). Please read them carefully before using the Site.
</p>
<div class="flex items-start gap-3 rounded-xl bg-slate-50 border border-slate-200 p-4 text-sm text-slate-500">
<i class="fa-solid fa-circle-info mt-0.5 text-slate-400"></i>
<p>
These terms are standard boilerplate and will be finalized following legal review.
Certain details — for example, the governing jurisdiction below — are placeholders
pending that review.
</p>
</div>
<!-- 1. Acceptance of terms -->
<section>
<h2 class="text-xl font-bold text-slate-900 mb-3 flex items-center gap-3">
<span class="inline-flex items-center justify-center w-7 h-7 rounded-lg bg-brand-light text-brand text-sm font-bold shrink-0">1</span>
Acceptance of these terms
</h2>
<div class="space-y-3">
<p class="text-slate-600 leading-relaxed">
By accessing or using the Site, you agree to be bound by these Terms and our
<a href="/Privacy" class="text-brand font-semibold hover:underline">Privacy Policy</a>.
If you do not agree to them, please discontinue use of the Site.
</p>
<p class="text-slate-600 leading-relaxed">
These terms apply to everyone who uses the Site, including anyone who submits a
demo request or a message through a contact form.
</p>
</div>
</section>
<!-- 2. Services & product access -->
<section>
<h2 class="text-xl font-bold text-slate-900 mb-3 flex items-center gap-3">
<span class="inline-flex items-center justify-center w-7 h-7 rounded-lg bg-brand-light text-brand text-sm font-bold shrink-0">2</span>
Services and product access
</h2>
<div class="space-y-3">
<p class="text-slate-600 leading-relaxed">
The Site provides information about our products and a way to request a
demonstration or to contact our team. It does not, by itself, grant you any right
to use any Plus Health Tech software or platform.
</p>
<p class="text-slate-600 leading-relaxed">
Access to our Clinical Decision Support, Patient Management, or Veterinary products —
where offered — is governed by a separate agreement, and may be subject to licensing,
eligibility, and data-handling requirements. We may modify, suspend, or discontinue any
feature of the Site at any time.
</p>
</div>
</section>
<!-- 3. Clinical decision support disclaimer -->
<section class="rounded-xl bg-brand-light/40 border border-brand/15 p-6 sm:p-8">
<h2 class="text-xl font-bold text-slate-900 mb-3 flex items-center gap-3">
<span class="inline-flex items-center justify-center w-7 h-7 rounded-lg bg-brand text-white text-sm font-bold shrink-0">3</span>
Clinical decision support disclaimer
</h2>
<div class="space-y-3">
<p class="text-slate-700 leading-relaxed">
Plus Health Tech's products are Clinical Decision Support (CDS) tools. Their output is
decision <em>support</em> only — it is intended to <strong>assist, not replace,</strong>
the professional judgment of qualified clinicians.
</p>
<p class="text-slate-700 leading-relaxed">
CDS output is not a diagnosis, a treatment plan, or medical advice, and it must never be
the sole basis for a clinical decision. Clinicians who rely on any output remain
responsible for exercising independent professional judgment in the diagnosis, treatment,
and care of their patients — human or animal.
</p>
</div>
</section>
<!-- 4. Intellectual property -->
<section>
<h2 class="text-xl font-bold text-slate-900 mb-3 flex items-center gap-3">
<span class="inline-flex items-center justify-center w-7 h-7 rounded-lg bg-brand-light text-brand text-sm font-bold shrink-0">4</span>
Intellectual property
</h2>
<div class="space-y-3">
<p class="text-slate-600 leading-relaxed">
All content on the Site — including text, graphics, logos, icons, and software — is the
property of Plus Health Tech or its licensors and is protected by applicable copyright,
trademark, and other intellectual-property laws. You are granted a limited, non-exclusive
right to view and use the Site for lawful purposes, and nothing transfers ownership to you.
</p>
<p class="text-slate-600 leading-relaxed">
All trademarks, service marks, and product names referenced (including standards such as
FHIR&reg; and HL7&reg;) are the property of their respective owners and are used for
identification only. We reserve all rights not expressly granted.
</p>
</div>
</section>
<!-- 5. Acceptable use -->
<section>
<h2 class="text-xl font-bold text-slate-900 mb-3 flex items-center gap-3">
<span class="inline-flex items-center justify-center w-7 h-7 rounded-lg bg-brand-light text-brand text-sm font-bold shrink-0">5</span>
Acceptable use
</h2>
<div class="space-y-3">
<p class="text-slate-600 leading-relaxed">
You agree to use the Site lawfully and not to:
</p>
<ul class="list-disc pl-5 space-y-1.5 text-slate-600 leading-relaxed">
<li>use the Site in any way that violates an applicable law or regulation;</li>
<li>attempt to access, disrupt, or damage any part of the Site or the systems behind it;</li>
<li>upload or transmit material containing malware or malicious code;</li>
<li>scrape or collect content for commercial purposes without our permission; or</li>
<li>use the Site to send unsolicited or fraudulent communications.</li>
</ul>
<p class="text-slate-600 leading-relaxed">
We may terminate or restrict access for conduct we reasonably believe violates these terms.
</p>
</div>
</section>
<!-- 6. Limitation of liability (LEGAL REVIEW) -->
<section>
<h2 class="text-xl font-bold text-slate-900 mb-3 flex items-center gap-3">
<span class="inline-flex items-center justify-center w-7 h-7 rounded-lg bg-brand-light text-brand text-sm font-bold shrink-0">6</span>
Limitation of liability
</h2>
<div class="space-y-3">
<p class="text-slate-600 leading-relaxed">
The Site and its content are provided on an "as is" and "as available" basis, without
warranties of any kind, whether express or implied, including as to accuracy, completeness,
fitness for a particular purpose, or non-infringement.
</p>
<p class="text-slate-600 leading-relaxed">
To the maximum extent permitted by law, Plus Health Tech and its officers, employees, and
licensors shall not be liable for any indirect, incidental, special, consequential, or
punitive damages, or any loss of profits or data, arising out of or related to your use of
the Site.
</p>
</div>
</section>
<!-- 7. Governing law (LEGAL REVIEW — placeholder jurisdiction) -->
<section>
<h2 class="text-xl font-bold text-slate-900 mb-3 flex items-center gap-3">
<span class="inline-flex items-center justify-center w-7 h-7 rounded-lg bg-brand-light text-brand text-sm font-bold shrink-0">7</span>
Governing law
</h2>
<div class="space-y-3">
<p class="text-slate-600 leading-relaxed">
Except where a separate agreement provides otherwise, these Terms and any dispute arising
from your use of the Site are governed by the laws of
<span class="inline-flex items-center gap-1 align-middle mx-1 px-2 py-0.5 rounded bg-amber-100 text-amber-800 text-xs font-semibold">
<i class="fa-solid fa-flag"></i> [Jurisdiction to be confirmed]
</span>,
without regard to its conflict-of-law principles.
</p>
</div>
</section>
<!-- 8. Changes to these terms -->
<section>
<h2 class="text-xl font-bold text-slate-900 mb-3 flex items-center gap-3">
<span class="inline-flex items-center justify-center w-7 h-7 rounded-lg bg-brand-light text-brand text-sm font-bold shrink-0">8</span>
Changes to these terms
</h2>
<div class="space-y-3">
<p class="text-slate-600 leading-relaxed">
We may revise these Terms from time to time. If we make material changes, we will update
the "Last updated" date at the top of this page and, where appropriate, post a notice on the
Site. Your continued use of the Site after changes take effect constitutes acceptance of
the revised terms.
</p>
</div>
</section>
<!-- 9. Contact -->
<section>
<h2 class="text-xl font-bold text-slate-900 mb-3 flex items-center gap-3">
<span class="inline-flex items-center justify-center w-7 h-7 rounded-lg bg-brand-light text-brand text-sm font-bold shrink-0">9</span>
Contact
</h2>
<div class="space-y-3">
<p class="text-slate-600 leading-relaxed">
Questions about these Terms &amp; Conditions? Reach us through our
<a href="/Contact" class="text-brand font-semibold hover:underline">contact page</a>,
and we'll point you in the right direction.
</p>
</div>
</section>
</article>
</div>
</section>
+19
View File
@@ -0,0 +1,19 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace plushealthtech.Pages
{
public class TermsModel : PageModel
{
private readonly ILogger<TermsModel> _logger;
public TermsModel(ILogger<TermsModel> logger)
{
_logger = logger;
}
public void OnGet()
{
}
}
}
+107
View File
@@ -0,0 +1,107 @@
@page
@model VeterinaryModel
@{
ViewData["Title"] = "Veterinarian Solutions";
}
<!-- Hero Section -->
<section class="relative overflow-hidden pt-16 pb-20 lg:pt-24 lg:pb-24 bg-gradient-to-b from-white via-slate-50 to-teal-50">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
<div class="max-w-3xl mx-auto space-y-4 text-center">
<span class="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-teal-50 text-brand-vet text-xs font-bold uppercase tracking-wider">
<i class="fa-solid fa-paw"></i> Animal Clinic Platform
</span>
<h1 class="text-4xl sm:text-5xl font-extrabold text-slate-900 tracking-tight leading-tight">
Purpose-built software <span class="text-brand-vet">for animal clinics</span>
</h1>
<p class="text-lg text-slate-600">
One connected platform for veterinary teams — EHR records, imaging, and diagnostic decision tools shaped around the species you care for, every day.
</p>
<div class="flex flex-col sm:flex-row items-center justify-center gap-4 pt-4">
<a href="/RequestDemo" class="w-full sm:w-auto bg-brand-vet hover:bg-teal-700 text-white font-semibold px-8 py-4 rounded-xl shadow-lg shadow-brand-vet/25 transition-all text-center">
Request a demo <i class="fa-solid fa-arrow-right ml-2"></i>
</a>
<a href="/ClinicalDecisionSupport" class="w-full sm:w-auto bg-white hover:bg-slate-50 text-slate-700 font-semibold px-8 py-4 rounded-xl border border-slate-200 transition-all text-center">
Explore Clinical Decision Support
</a>
</div>
</div>
</div>
</section>
<!-- Features Section -->
<section id="features" class="py-24 bg-white">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="text-center max-w-3xl mx-auto space-y-4 mb-16">
<h2 class="text-xs font-bold tracking-widest text-brand-vet uppercase">Veterinarian Solutions</h2>
<h3 class="text-3xl font-extrabold text-slate-900 tracking-tight">Everything an animal clinic needs, in one connected platform</h3>
<p class="text-slate-600">From the front desk to the imaging suite — purpose-built tools that keep practice operations, clinical records, and diagnostic guidance moving as one.</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
<!-- Card 1 -->
<div class="bg-slate-50 p-8 rounded-2xl border border-slate-100 hover:shadow-xl transition-all group">
<div class="w-14 h-14 rounded-xl bg-teal-50 text-brand-vet flex items-center justify-center text-xl font-bold mb-6 group-hover:bg-brand-vet group-hover:text-white transition-colors">
<i class="fa-solid fa-user-group"></i>
</div>
<h4 class="text-xl font-bold text-slate-900 mb-3">Practice &amp; Client Management</h4>
<p class="text-slate-600 text-sm leading-relaxed">
Scheduling, check-in, and owner communication built for the pace of a busy veterinary practice — so appointments flow smoothly and clients stay informed between visits.
</p>
</div>
<!-- Card 2 -->
<div class="bg-slate-50 p-8 rounded-2xl border border-slate-100 hover:shadow-xl transition-all group">
<div class="w-14 h-14 rounded-xl bg-teal-50 text-brand-vet flex items-center justify-center text-xl font-bold mb-6 group-hover:bg-brand-vet group-hover:text-white transition-colors">
<i class="fa-solid fa-file-medical"></i>
</div>
<h4 class="text-xl font-bold text-slate-900 mb-3">EHR Records &amp; Imaging</h4>
<p class="text-slate-600 text-sm leading-relaxed">
A complete medical home for every patient — history, labs, radiographs, and ultrasound organized in one chart and ready the moment you open a case.
</p>
</div>
<!-- Card 3 -->
<div class="bg-slate-50 p-8 rounded-2xl border border-slate-100 hover:shadow-xl transition-all group">
<div class="w-14 h-14 rounded-xl bg-teal-50 text-brand-vet flex items-center justify-center text-xl font-bold mb-6 group-hover:bg-brand-vet group-hover:text-white transition-colors">
<i class="fa-solid fa-paw"></i>
</div>
<h4 class="text-xl font-bold text-slate-900 mb-3">Species-Tailored Decision Tools</h4>
<p class="text-slate-600 text-sm leading-relaxed">
Diagnostic guidance tuned to the species and conditions you see — reference ranges, dosing checks, and interpretations built for animal patients, not adapted from human workflows.
</p>
</div>
<!-- Card 4 -->
<div class="bg-slate-50 p-8 rounded-2xl border border-slate-100 hover:shadow-xl transition-all group">
<div class="w-14 h-14 rounded-xl bg-teal-50 text-brand-vet flex items-center justify-center text-xl font-bold mb-6 group-hover:bg-brand-vet group-hover:text-white transition-colors">
<i class="fa-solid fa-circle-nodes"></i>
</div>
<h4 class="text-xl font-bold text-slate-900 mb-3">Interoperability With Hospital Systems</h4>
<p class="text-slate-600 text-sm leading-relaxed">
Connects to the hospital systems you already run through FHIR® and HL7v2 — no rip-and-replace, no parallel records, no data silos.
</p>
</div>
</div>
</div>
</section>
<!-- CTA Section -->
<section class="py-24 bg-slate-50">
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="bg-gradient-to-br from-brand-vet to-teal-700 text-white rounded-2xl p-10 sm:p-14 shadow-xl shadow-brand-vet/20 text-center space-y-4">
<h2 class="text-3xl font-extrabold tracking-tight">See veterinary solutions working in your practice</h2>
<p class="text-teal-50 max-w-2xl mx-auto">
Book a walkthrough with our team and watch the full clinic workflow — records, imaging, and diagnostic guidance — run the way your practice actually operates.
</p>
<div class="flex flex-col sm:flex-row items-center justify-center gap-4 pt-2">
<a href="/RequestDemo" class="w-full sm:w-auto bg-white text-brand-vet hover:bg-teal-50 font-semibold px-8 py-4 rounded-xl shadow-md transition-all text-center">
Request a demo <i class="fa-solid fa-arrow-right ml-2"></i>
</a>
<a href="/ClinicalDecisionSupport" class="w-full sm:w-auto text-white font-semibold px-8 py-4 rounded-xl border border-white/40 hover:bg-white/10 transition-all text-center">
Explore Clinical Decision Support
</a>
</div>
</div>
</div>
</section>
+19
View File
@@ -0,0 +1,19 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace plushealthtech.Pages
{
public class VeterinaryModel : PageModel
{
private readonly ILogger<VeterinaryModel> _logger;
public VeterinaryModel(ILogger<VeterinaryModel> logger)
{
_logger = logger;
}
public void OnGet()
{
}
}
}
+7
View File
@@ -1,8 +1,15 @@
using plushealthtech.Mail;
using Microsoft.Extensions.Options;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
// Outbound mail (SMTP / MailPit) for the Contact and Request Demo forms.
builder.Services.Configure<SmtpOptions>(builder.Configuration.GetSection("Smtp"));
builder.Services.AddSingleton<MailSender>();
var app = builder.Build();
// Configure the HTTP request pipeline.
+7 -1
View File
@@ -5,5 +5,11 @@
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
"AllowedHosts": "*",
"Smtp": {
"Host": "tools.host.domain",
"Port": 1025,
"From": "[email protected]",
"To": "[email protected]"
}
}
@@ -0,0 +1,9 @@
@import "tailwindcss";
@theme {
--color-brand: #1c8098;
--color-brand-dark: #145f72;
--color-brand-light: #e6f2f5;
--color-brand-accent: #0ea5e9;
--color-brand-vet: #0d9488;
}
+17
View File
@@ -0,0 +1,17 @@
{
"version": 1,
"skills": {
"find-skills": {
"source": "vercel-labs/skills",
"sourceType": "github",
"skillPath": "skills/find-skills/SKILL.md",
"computedHash": "b146008599c31057cef1c145774cea5d5afb30e8f43fa802e47a4b461419aaaf"
},
"frontend-design": {
"source": "anthropics/skills",
"sourceType": "github",
"skillPath": "skills/frontend-design/SKILL.md",
"computedHash": "93f53fd1c0352d3d7ec17b8f73d7351bf76f4821e55d123c9d2abc2c53ba48e4"
}
}
}
+50
View File
@@ -0,0 +1,50 @@
# Task 01 — SMTP / MailPit configuration
- **Status:** Done — 2026-08-20
- **Depends on:** —
- **Size:** S
## Goal
Make the existing `Mail/` infrastructure (already in the tree and registered in `Program.cs`) actually usable: add the missing `"Smtp"` configuration section so `MailSender` has a host, port, sender, and recipient.
## Context
- `plushealthtech/Mail/MailSender.cs` — sends via `SmtpClient`, `EnableSsl = false`, no credentials. **Do not recreate or rewire** — it is registered as a singleton in `Program.cs` from the `"Smtp"` section.
- `plushealthtech/Mail/SmtpOptions.cs` — `Host` (default `localhost`), `Port` (default `1025`), `From` / `To` (default empty → `MailSender` throws if submitted while empty).
- **No `"Smtp"` section exists in any appsettings file** — that is the gap.
## Files
- Modify: `plushealthtech/appsettings.json`
## Steps
1. Add this section to `plushealthtech/appsettings.json` (base config — the MailPit server is the same in every environment):
```json
"Smtp": {
"Host": "tools.host.domain",
"Port": 1025,
"From": "[email protected]",
"To": "[email protected]"
}
```
`From` is an assumption (MailPit does not validate the sender) — single point of change if a different sender address is wanted.
2. `dotnet build plushealthtech.sln` — must stay green (no code changes expected).
3. Confirm `tools.host.domain` resolves from this machine (`nslookup tools.host.domain` or `ping -n 1 tools.host.domain`). If it only resolves on the internal network, note that dev machines will need a hosts entry.
## Acceptance criteria
- [x] `appsettings.json` contains the `Smtp` section exactly as above.
- [x] Build green.
- [x] Host name resolution confirmed (or documented as internal-network-only).
- [ ] End-to-end delivery proven in Task 13 (form submit → message visible in MailPit for `alerts@plushealthtech.local`).
## Notes
- `From` empty would throw `ArgumentException` in `MailMessage` — this task removes that failure mode.
- Resolution check (2026-08-20): `tools.host.domain` resolves to `10.10.0.30` via the internal router DNS (`vrouter.home.domain`, 10.10.0.1) and answers ping; TCP port 1025 is open. It is **internal-network-only** — dev machines off that network will need a hosts entry for `tools.host.domain`.
+52
View File
@@ -0,0 +1,52 @@
# Task 02 — Contact page with MailPit form
- **Status:** Done — 2026-08-20
- **Depends on:** 01
- **Size:** M
## Goal
Create the **Contact** page at `/Contact` with a form that emails its submission to MailPit (`tools.host.domain:1025`, recipient `[email protected]`) via the existing `MailSender`.
## Files
- Create: `plushealthtech/Pages/Contact.cshtml`
- Create: `plushealthtech/Pages/Contact.cshtml.cs`
## Steps
1. **PageModel** (`ContactModel : PageModel`)
- Inject `MailSender` (+ `ILogger<ContactModel>`).
- `[BindProperty]` fields with DataAnnotations:
- `Name` — `[Required]`, `[StringLength(200)]`
- `Email` — `[Required]`, `[EmailAddress]`, `[StringLength(320)]`
- `Organization` — `[StringLength(200)]` (optional)
- `Phone` — `[StringLength(50)]` (optional)
- `Message` — `[Required]`, `[StringLength(5000)]`
- `public string? Success { get; set; }` and `public string? Error { get; set; }` (simple re-render flags).
- `OnGet()` — reset flags.
- `OnPost()`:
- If `!ModelState.IsValid` → return `Page()` (validation messages re-render).
- Else build a plain-text body: one `Field: value` line per non-empty field, blank line, then the message.
- `await _mailSender.SendAsync($"New contact form message — {Model.Name}", body);`
- On success: `Success = "Thanks — your message has been sent. We'll reply to {Email}."` (re-render page with the panel).
- Catch `SmtpException` (or general `Exception`) → log the exception, set `Error` to a generic "Your message could not be sent right now. Please try again or email us directly." — never surface SMTP details to the user.
2. **View** (`Contact.cshtml`)
- `@page`, `@model ContactModel`, `ViewData["Title"] = "Contact"`.
- Layout consistent with the Index page: light hero (heading + one-line intro), then a single form card (`bg-white p-8 rounded-2xl border border-slate-100 shadow-sm`) with the fields, Tailwind classes matching the site's existing inputs (`rounded-xl`, `border-slate-200`, `focus:border-brand` style), a submit button in brand color (`bg-[#1c8098] hover:bg-[#145f72] rounded-xl`).
- `@Html.AntiforgeryToken()` in the form.
- Validation messages per field (`<span asp-validation-for="...">`); `Success` panel (green, replaces/above the form) and `Error` banner (red) rendered when set.
- A short side note block: "We reply to the email address you provide — nothing else is stored."
## Acceptance criteria
- [x] `GET /Contact` renders with all five fields; build green.
- [x] Empty required fields / invalid email → validation messages, no mail sent.
- [x] Valid submit → success panel shown; MailPit shows the message from `[email protected]` to `[email protected]` with subject `New contact form message — {Name}` (e2e verified 2026-08-20, ahead of Task 13).
- [x] With MailPit down → friendly error banner, exception in app log, no stack trace in the browser.
## Notes
- Antiforgery: the form uses `asp-page="/Contact"`, which auto-injects `__RequestVerificationToken` — the explicit `@Html.AntiforgeryToken()` from the spec is unnecessary and would duplicate the field.
- SMTP-down path verified by running the app with `Smtp__Port=1` (env override, no config change): banner rendered, `SmtpException (SocketException 10061)` logged, response body clean.
+47
View File
@@ -0,0 +1,47 @@
# Task 03 — Request Demo page with MailPit form
- **Status:** Done — 2026-08-20
- **Depends on:** 01
- **Size:** M
## Goal
Create the **Request Demo** page at `/RequestDemo` with a form that emails its submission to MailPit (`tools.host.domain:1025`, recipient `[email protected]`) via the existing `MailSender`. Same send/success/error pattern as the Contact page (Task 02).
## Files
- Create: `plushealthtech/Pages/RequestDemo.cshtml`
- Create: `plushealthtech/Pages/RequestDemo.cshtml.cs`
## Steps
1. **PageModel** (`RequestDemoModel : PageModel`)
- Inject `MailSender` (+ logger).
- `[BindProperty]` fields with DataAnnotations:
- `Name` — `[Required]`, `[StringLength(200)]`
- `Email` — `[Required]`, `[EmailAddress]`, `[StringLength(320)]`
- `Organization` — `[Required]`, `[StringLength(200)]`
- `PracticeType` — select: Healthcare system, Hospital, Clinic, Veterinary practice, Other (optional)
- `ProductsOfInterest` — select: Clinical Decision Support, Patient Management, Veterinary Solutions, Multiple / not sure (optional)
- `Message` — `[StringLength(5000)]` (optional)
- `Success` / `Error` flags, `OnGet` reset.
- `OnPost()` same contract as Task 02: validate → build plain-text `Field: value` body → `SendAsync($"New demo request — {Organization}", body)` → success flag; SMTP failure → generic error banner + log.
2. **View** (`RequestDemo.cshtml`)
- `@page`, `@model RequestDemoModel`, `ViewData["Title"] = "Request Demo"`.
- Marketing-flavored hero (one-line value prop, 2–3 trust bullets like "FHIR®-ready" / "24/7 clinical support" — reuse Index hero metrics) + the form card, Tailwind styling consistent with the site.
- `@Html.AntiforgeryToken()`, per-field validation spans, success/error rendering identical in behavior to Contact.
- Close with a secondary CTA: "Prefer to talk? Visit the Contact page." → `/Contact`.
## Acceptance criteria
- [x] `GET /RequestDemo` renders; build green.
- [x] Required fields enforced; selects render all options.
- [x] Valid submit → success panel shown; MailPit shows the message from `[email protected]` to `[email protected]` with subject `New demo request — {Organization}` (e2e verified 2026-08-20, ahead of Task 13).
- [x] With MailPit down → friendly error banner, exception in app log, no stack trace in the browser.
## Notes
- Antiforgery: the form uses `asp-page="/RequestDemo"`, which auto-injects `__RequestVerificationToken` — the explicit `@Html.AntiforgeryToken()` from the spec is unnecessary and would duplicate the field (same approach as the Contact page).
- SMTP-down path verified by running the app with `Smtp__Host=127.0.0.1 Smtp__Port=1` (env override, no config change): banner rendered, `SmtpException (SocketException 10061)` logged with sender context, response body clean.
- Validation confirmed over HTTP: empty POST → "required" messages on Name/Email/Organization only; bad email → "not a valid e-mail address"; both selects render their full option lists with a placeholder.
+35
View File
@@ -0,0 +1,35 @@
# Task 04 — Clinical Decision Support page
- **Status:** Done
- **Depends on:** —
- **Size:** M
## Goal
Complete the **Clinical Decision Support** page at `/ClinicalDecisionSupport`. The PageModel already exists in the tree — only the view is missing.
## Current state
- `plushealthtech/Pages/ClinicalDecisionSupport.cshtml.cs` — `ClinicalDecisionSupportModel` with an empty `OnGet()`. Keep it; no model changes needed (the view sets `ViewData["Title"]`).
- **`ClinicalDecisionSupport.cshtml` does not exist** → the route currently 404s.
## Files
- Create: `plushealthtech/Pages/ClinicalDecisionSupport.cshtml`
- (Optional) `plushealthtech/Pages/ClinicalDecisionSupport.cshtml.cs` — only if you want `OnGet` to set the title server-side.
## Steps
1. Create the view: `@page`, `@model ClinicalDecisionSupportModel`, `ViewData["Title"] = "Clinical Decision Support"`.
2. Sections (copy aligned with Index card #1 and the `#interoperability` section — reword, don't paste verbatim):
- **Hero** — "Evidence-based guidance at the point of care" + one-line subhead + CTA button → `/RequestDemo` ("Request a demo").
- **Feature cards** (3–4, `rounded-2xl border border-slate-100` cards): intelligent algorithms that reduce medical errors; optimized treatment choices; alerting & workflow integration; FHIR®/HL7v2 interoperability.
- **Interoperability band** (dark `bg-slate-900` like the Index section) — standards-based data exchange messaging.
- **CTA band** → `/RequestDemo`, secondary link → `/PatientManagement`.
3. Use existing Tailwind brand tokens (`text-brand`, `bg-brand-light`, `brand-accent`); no new CSS.
## Acceptance criteria
- [ ] `GET /ClinicalDecisionSupport` renders (no 404); build green.
- [ ] Page links to `/RequestDemo` (and optionally `/PatientManagement`) resolve.
- [ ] Styling consistent with Index page sections.
+29
View File
@@ -0,0 +1,29 @@
# Task 05 — Patient Management page
- **Status:** Done
- **Depends on:** —
- **Size:** M
## Goal
Create the **Patient Management** page at `/PatientManagement` — a content page in the same visual language as the Index page.
## Files
- Create: `plushealthtech/Pages/PatientManagement.cshtml`
- Create: `plushealthtech/Pages/PatientManagement.cshtml.cs`
## Steps
1. **PageModel** — standard `PageModel` with `OnGet()` (mirror `ClinicalDecisionSupportModel`); `ViewData["Title"]` set in the view.
2. **View** — `@page`, `@model PatientManagementModel`, `ViewData["Title"] = "Patient Management"`.
- **Hero** — "End-to-end patient workflow, from intake to discharge" + subhead (reword Index card #2) + CTA → `/RequestDemo`.
- **Feature cards** (3–4): intake & registration; scheduling & capacity; transparent patient communication channels; EHR integration (FHIR®-ready) — consistent `rounded-2xl border border-slate-100` cards.
- **CTA band** → `/RequestDemo`, secondary link → `/ClinicalDecisionSupport`.
3. Tailwind brand tokens only (`brand`, `brand-light`, `brand-accent`); no new CSS, no new JS.
## Acceptance criteria
- [x] `GET /PatientManagement` renders; build green.
- [x] CTAs link to existing/planned routes (`/RequestDemo`, `/ClinicalDecisionSupport`).
- [x] Styling consistent with Index and the CDS page.
+33
View File
@@ -0,0 +1,33 @@
# Task 06 — Veterinarian Solutions page
- **Status:** Complete
- **Depends on:** —
- **Size:** M
## Goal
Create the **Veterinarian Solutions** page at `/Veterinary`, using the veterinary sub-brand accent (`brand-vet` / `#0d9488`) per `Design.MD`.
## Files
- Create: `plushealthtech/Pages/Veterinary.cshtml`
- Create: `plushealthtech/Pages/Veterinary.cshtml.cs`
## Steps
1. **PageModel** — standard `PageModel` with `OnGet()`; title set in the view.
2. **View** — `@page`, `@model VeterinaryModel`, `ViewData["Title"] = "Veterinarian Solutions"`.
- **Hero** — "Purpose-built software for animal clinics" + subhead (reword Index card #3) + CTA → `/RequestDemo`.
- **Feature cards** (3–4): practice & client management; EHR records & imaging; species-tailored diagnostic decision tools; interoperability with existing hospital systems.
- **Accent rules** — use `brand-vet` (`#0d9488`) for the hero badge, card icon tiles (`bg-teal-50 text-brand-vet`, hover `bg-brand-vet text-white`) exactly like Index card #3, instead of the default `brand` teal.
- **CTA band** → `/RequestDemo`, secondary link → `/ClinicalDecisionSupport`.
## Acceptance criteria
- [x] `GET /Veterinary` renders; build green.
- [x] Page visibly uses the veterinary accent, distinct from the other product pages.
- [x] CTAs resolve.
## Notes
- The Index solution card carries `id="veterinary"` for the legacy nav anchor — leave that in place; the page route and the in-page anchor don't conflict.
+33
View File
@@ -0,0 +1,33 @@
# Task 07 — About Us page
- **Status:** Done (2026-08-20)
- **Depends on:** —
- **Size:** S/M
## Goal
Create the **About Us** page at `/About`.
## Files
- Create: `plushealthtech/Pages/About.cshtml`
- Create: `plushealthtech/Pages/About.cshtml.cs`
## Steps
1. **PageModel** — standard `PageModel` with `OnGet()`; title set in the view.
2. **View** — `@page`, `@model AboutModel`, `ViewData["Title"] = "About Us"`.
- **Hero** — who we are: Plus Health Tech builds interoperable clinical software; tone per `Design.MD` (trust, clinical precision, seamless interoperability, compassionate care).
- **What we do** — three product pillars as cards, each linking to its page:
- Clinical Decision Support → `/ClinicalDecisionSupport`
- Patient Management → `/PatientManagement`
- Veterinarian Solutions → `/Veterinary`
- **Values** (3–4 short items): interoperability by default, evidence-based, patient-first, built with clinicians.
- **CTA band** → `/RequestDemo`, secondary → `/Careers`.
3. Tailwind brand tokens only; no new CSS/JS.
## Acceptance criteria
- [x] `GET /About` renders; build green.
- [x] All three pillar cards link to their product pages; CTAs resolve. (Note: `/Careers` is a forward link — page lands with task 08.)
- [x] Copy matches the brand voice in `Design.MD` (reviewed against it before marking done).
+30
View File
@@ -0,0 +1,30 @@
# Task 08 — Careers page
- **Status:** Done
- **Depends on:** —
- **Size:** S
## Goal
Create the **Careers** page at `/Careers`. No job-board data or backend exists — this is a culture page with an "apply via the Contact page" path (assumption per plan §3.6).
## Files
- Create: `plushealthtech/Pages/Careers.cshtml`
- Create: `plushealthtech/Pages/Careers.cshtml.cs`
## Steps
1. **PageModel** — standard `PageModel` with `OnGet()`; title set in the view.
2. **View** — `@page`, `@model CareersModel`, `ViewData["Title"] = "Careers"`.
- **Hero** — "Build the future of interoperable clinical software."
- **Why join us** — 3–4 cards (mission-driven work, clinical+technical collaboration, modern .NET stack, flexibility — keep claims generic and reviewable).
- **How to apply** — explicit note that there is no public job board right now: "Tell us about yourself via the Contact page and we'll be in touch." CTA button → `/Contact`.
- Secondary CTA → `/About`.
3. Tailwind brand tokens only; no new CSS/JS.
## Acceptance criteria
- [x] `GET /Careers` renders; build green.
- [x] Apply CTA points at `/Contact`; no dead links.
- [x] No fabricated specific claims (salaries, exact team size) — keep it generic for legal/brand review.
+37
View File
@@ -0,0 +1,37 @@
# Task 09 — Terms & Conditions page
- **Status:** Done
- **Depends on:** —
- **Size:** S/M
## Goal
Create the **Terms & Conditions** page at `/Terms` with standard corporate boilerplate (flagged for legal review before production — plan §3.6).
## Files
- Create: `plushealthtech/Pages/Terms.cshtml`
- Create: `plushealthtech/Pages/Terms.cshtml.cs`
## Steps
1. **PageModel** — standard `PageModel` with `OnGet()`; title set in the view.
2. **View** — `@page`, `@model TermsModel`, `ViewData["Title"] = "Terms & Conditions"`.
- Last-updated date line (2026).
- Sections (h2 + short paragraphs, standard corporate wording):
1. Acceptance of terms
2. Services & product access (landing site, demo requests, contact forms)
3. **Clinical decision support disclaimer** — CDS output is decision *support*; not a substitute for professional clinical judgment, diagnosis, or treatment (important given the product line)
4. Intellectual property
5. User conduct / acceptable use
6. Limitation of liability
7. Governing law (placeholder jurisdiction — mark for legal)
8. Changes to these terms
9. Contact (→ `/Contact`)
3. Simple prose layout (max-w-3xl, readable line length); Tailwind only.
## Acceptance criteria
- [x] `GET /Terms` renders; build green. (verified: `dotnet build` 0 warnings/0 errors; live `GET /Terms` → HTTP 200)
- [x] Contains the CDS disclaimer section. (section 3 — highlighted; "decision support only", "not a substitute", "human or animal")
- [x] No live legal claims left unflagged — placeholders marked (e.g., jurisdiction) for review. (visible "finalized following legal review" callout + `[Jurisdiction to be confirmed]` pill + `<!-- legal -->` comments on §§6–7)
+32
View File
@@ -0,0 +1,32 @@
# Task 10 — Sign In page (placeholder)
- **Status:** Done
- **Depends on:** —
- **Size:** S
## Goal
Create the **Sign In** page at `/SignIn` as a placeholder. The repo has **no auth** (per CLAUDE.md) and no account backend — real sign-in is a separate project; this page is its future integration point (plan §3.4).
## Files
- Create: `plushealthtech/Pages/SignIn.cshtml`
- Create: `plushealthtech/Pages/SignIn.cshtml.cs`
## Steps
1. **PageModel** — standard `PageModel` with `OnGet()`; title set in the view.
2. **View** — `@page`, `@model SignInModel`, `ViewData["Title"] = "Sign In"`.
- Centered card (`bg-white p-8 rounded-2xl border border-slate-100 shadow-sm`, max-w-md):
- Icon (e.g. `fa-solid fa-right-to-bracket`) + heading "Sign in to your portal"
- Status line: "Portal sign-in is coming soon."
- Primary CTA → `/RequestDemo` ("Request a demo")
- Secondary CTA → `/Contact` ("Contact us instead")
- Do **not** add a fake username/password form — it would imply working auth.
3. Tailwind brand tokens only.
## Acceptance criteria
- [x] `GET /SignIn` renders; build green.
- [x] Both CTAs resolve.
- [x] No non-functional credentials form.
+39
View File
@@ -0,0 +1,39 @@
# Task 11 — Privacy Policy content refresh
- **Status:** Done — 2026-08-20
- **Depends on:** —
- **Size:** S
## Goal
Replace the template placeholder in the existing **Privacy** page (`/Privacy`, page already ships) with real policy content (flagged for legal review — plan §3.6).
## Files
- Modify: `plushealthtech/Pages/Privacy.cshtml`
- (No PageModel change needed — `PrivacyModel` already exists.)
## Current state
`plushealthtech/Pages/Privacy.cshtml` currently renders only:
> Use this page to detail your site's privacy policy.
## Steps
1. Keep `@page`, `@model PrivacyModel`, `ViewData["Title"] = "Privacy Policy"` and the route `/Privacy` (already linked nowhere yet — Task 12 will link it).
2. Replace the body with standard sections (h2 + paragraphs, max-w-3xl prose layout):
- **Information we collect** — the Contact and Request Demo form fields (name, email, organization, phone, practice type, products of interest, message).
- **How we use it** — to respond to inquiries and demo requests; messages are delivered to our team mailbox (`[email protected]`); no third-party sale/sharing.
- **Third-party services** — Font Awesome (cdnjs), analytics (analytics.rokoh.com), blog subdomain (blog.plushealthtech.com) — describe in one line each.
- **Cookies** — analytics cookies set by the above; no session cookies (no auth).
- **Data retention** — placeholder wording, marked for legal.
- **Your rights** — access/correction/deletion requests via the Contact page.
- **Contact** — link → `/Contact`.
- Last-updated date line (2026).
3. Tailwind only; no new CSS/JS.
## Acceptance criteria
- [x] `GET /Privacy` shows the new policy, not the template sentence.
- [x] Accurately reflects the two forms' fields and the actual third parties in the layout `<head>`.
- [x] Route unchanged: `/Privacy`.
+69
View File
@@ -0,0 +1,69 @@
# Task 12 — Navigation, footer & CTA link wiring
- **Status:** Done
- **Depends on:** 02, 03, 04, 05, 06, 07, 08, 09, 10, 11 (all routes must exist first)
- **Size:** M
## Goal
Point every navbar, footer, and Index CTA at the real pages; remove the `#contact` anchor pattern and the dead `#` links; make the (currently non-functional) mobile menu work.
## Files
- Modify: `plushealthtech/Pages/Shared/_Layout.cshtml`
- Modify: `plushealthtech/Pages/Index.cshtml`
- (Optional, if JS approach chosen) `plushealthtech/wwwroot/js/mobile-menu.js` or an inline `<script>` in the layout's `Scripts` section.
## Steps
### 1. Desktop nav (`_Layout.cshtml`, the `hidden md:flex` nav)
Replace the current anchor links with:
| Label | href |
|---|---|
| CDS | `/ClinicalDecisionSupport` |
| Patient Management | `/PatientManagement` |
| Veterinary | `/Veterinary` |
| About | `/About` |
| Careers | `/Careers` |
| Contact | `/Contact` |
| Blog | `https://blog.plushealthtech.com` (keep, external) |
(Removes the `#solutions` / `#interoperability` / `#veterinary` / `#about` anchors from the nav — those sections remain reachable by scrolling on the Index page.)
### 2. CTA buttons (`_Layout.cshtml`)
- `Sign In` → `/SignIn` (was `#contact`).
- `Request Demo` → `/RequestDemo` (was `#contact`).
### 3. Footer (`_Layout.cshtml`)
- **Remove `id="contact"` from the `<footer>`** — nothing may target it anymore.
- Solutions column → `/ClinicalDecisionSupport`, `/PatientManagement`, `/Veterinary` (labels: Clinical Decision Support / Patient Management / Veterinarian Solutions).
- Company column → `/About`, `/Careers`, `/Contact`.
- **New Legal column** → `/Privacy` (Privacy Policy), `/Terms` (Terms & Conditions). Grid becomes `md:grid-cols-5` (or 4 + legal under Company — keep it tidy at md width).
- Newsletter input block: leave as-is (decorative, no endpoint specified — plan §3.8).
### 4. Index page (`Index.cshtml`)
- Hero primary CTA "Explore Platform" (`#contact`) → `/RequestDemo`.
- Hero secondary CTA "View Solutions" — keep `#solutions` (valid in-page anchor).
- Solution cards "Learn more" / "Explore Vet Tech" links (`#`) → `/ClinicalDecisionSupport`, `/PatientManagement`, `/Veterinary` respectively.
- Keep `id="solutions"`, `id="interoperability"`, `id="veterinary"` anchors on the sections (still used by the hero CTA and any deep links).
### 5. Mobile menu (currently a no-op `<button>`)
Add a minimal toggle so the desktop nav is reachable below `md`:
- Simplest robust option: `<details>`/`<summary>` wrapper around the nav, or a 5–10 line vanilla JS toggle (`classList.toggle('hidden')`) in the layout's `Scripts` section.
- Close the menu when a link is tapped (optional nicety).
- No framework, no new dependency.
## Acceptance criteria
- [x] Build green.
- [x] Desktop nav has 7 working links; CTAs point at `/SignIn` and `/RequestDemo`.
- [x] Footer columns link to all pages incl. new Legal column; footer no longer carries `id="contact"`.
- [x] Index: zero `href="#"` left (grep to confirm); hero CTA lands on `/RequestDemo`.
- [x] Mobile menu opens/closes and all links are reachable at 375px width. (verified via rendered markup, CSS class presence, and JS review — not a live browser click-through)
- [x] No new utility classes required → `wwwroot/css/tailwind.css` rebuild **not** needed (verify; if any new class slips in, run `npm run build:css`). (`pb-6` was new — rebuilt with `npm run build:css`.)
+45
View File
@@ -0,0 +1,45 @@
# Task 13 — Verification & final checks
- **Status:** Not started
- **Depends on:** all of the above
- **Size:** S
## Goal
Prove the whole feature set works end-to-end before the change set is considered done.
## Checklist
### Build & run
- [ ] `dotnet build plushealthtech.sln` → 0 errors, 0 warnings.
- [ ] `dotnet run --project plushealthtech` → site up at https://localhost:7070.
### Link sweep (all 10 pages)
- [ ] Routes resolve: `/ClinicalDecisionSupport`, `/PatientManagement`, `/Veterinary`, `/About`, `/Careers`, `/Contact`, `/RequestDemo`, `/SignIn`, `/Terms`, `/Privacy`.
- [ ] Desktop nav: all 7 links + both CTAs land on the right page.
- [ ] Mobile (≤768px): menu toggles open; every nav link reachable.
- [ ] Footer: Solutions / Company / Legal columns all resolve.
- [ ] Index: hero CTAs + three solution-card links resolve.
- [ ] Grep for leftovers: `grep -rn "href=\"#\"" plushealthtech/Pages` → empty; `grep -rn '#contact' plushealthtech/Pages` → empty.
- [ ] `/Privacy` renders the new policy content, not the template sentence.
### Mail e2e (both forms)
- [ ] MailPit reachable at `tools.host.domain:1025` (from this machine — if not, note the hosts-entry requirement from Task 01).
- [ ] Contact form: valid submit → success panel; message appears in MailPit from `[email protected]` to `[email protected]`, subject `New contact form message — {Name}`, body contains all fields.
- [ ] Request Demo form: valid submit → success panel; subject `New demo request — {Organization}`; body contains selects' values.
- [ ] Validation: empty required fields / bad email → inline errors, **no** message in MailPit.
- [ ] Failure path: stop MailPit → submit → friendly error banner (no stack trace); app log shows the `SmtpException`; restart MailPit → submit succeeds again.
### Hygiene
- [ ] `dotnet build` still green with the layout changes.
- [ ] CSS bundle unchanged (no new utility classes) — if one slipped in, `npm run build:css` and re-verify.
- [ ] No changes needed to `Dockerfile` or the Gitea workflow (image pipeline already compiles CSS and ships the whole `wwwroot` + app) — confirm by re-reading the diff if in doubt.
- [ ] Working tree reviewed: only the files listed in Tasks 01–12 changed.
## Definition of done
All boxes ticked, build green, both forms demonstrably delivering into MailPit, and every one of the 10 pages reachable from the navbar or footer.
+1
View File
@@ -0,0 +1 @@
global using Microsoft.Extensions.Logging;
+39
View File
@@ -0,0 +1,39 @@
using System.Net.Mail;
using Microsoft.Extensions.Options;
namespace plushealthtech.Mail
{
/// <summary>
/// Sends outbound email through the configured SMTP server (MailPit in local
/// development). No authentication is used.
/// </summary>
public class MailSender
{
private readonly SmtpOptions _options;
private readonly ILogger<MailSender> _logger;
public MailSender(IOptions<SmtpOptions> options, ILogger<MailSender> logger)
{
_options = options.Value;
_logger = logger;
}
public async Task SendAsync(string subject, string body)
{
using var client = new SmtpClient(_options.Host, _options.Port)
{
EnableSsl = false
};
var message = new MailMessage(_options.From, _options.To)
{
Subject = subject,
Body = body,
IsBodyHtml = false
};
await client.SendMailAsync(message);
_logger.LogInformation("Mail sent to {To} (subject: {Subject})", _options.To, subject);
}
}
}
+28
View File
@@ -0,0 +1,28 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using plushealthtech.Mail;
var options = Options.Create(new SmtpOptions
{
Host = "127.0.0.1",
Port = 2525,
From = "[email protected]",
To = "[email protected]"
});
var sender = new MailSender(options, NullLogger<MailSender>.Instance);
// Same shape as ContactModel.OnPostAsync: em dash in the subject, ASCII body
// plus one em dash in the message, as a user might type.
var subject = "New contact form message — Dr. Vera Test";
var body = string.Join("\n", new[]
{
"Name: Dr. José Ñ test",
"Email: [email protected]",
"",
"Task 13 e2e verification message — please ignore."
});
await sender.SendAsync(subject, body);
Console.WriteLine("sent");
+13
View File
@@ -0,0 +1,13 @@
namespace plushealthtech.Mail
{
/// <summary>
/// SMTP settings bound from the "Smtp" section of appsettings.json.
/// </summary>
public class SmtpOptions
{
public string Host { get; set; } = "localhost";
public int Port { get; set; } = 1025;
public string From { get; set; } = string.Empty;
public string To { get; set; } = string.Empty;
}
}
+16
View File
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.11" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.11" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.11" />
</ItemGroup>
</Project>
+27
View File
@@ -0,0 +1,27 @@
import socket, sys, threading
def handle(conn):
conn.sendall(b"220 sink ready\r\n")
buf = b""
while True:
data = conn.recv(65536)
if not data:
break
buf += data
if b"QUIT" in data:
break
open(r"C:\Code\Plushealthtech\tmp-mailtest\raw_capture.bin", "wb").write(buf)
try:
conn.sendall(b"221 bye\r\n")
except Exception:
pass
conn.close()
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(("127.0.0.1", 2525))
s.listen(1)
print("listening on 127.0.0.1:2525", flush=True)
c, _ = s.accept()
handle(c)
print("done", flush=True)