Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
8b2eb90
Replace site with Sprout v2 frontend
niklas-westman Apr 28, 2026
e63719d
Configure GitHub Pages release for www domain
niklas-westman Apr 28, 2026
ca39e8f
Make contact endpoint test respect build env
niklas-westman Apr 28, 2026
a8303bd
Make sitemap generation deterministic
niklas-westman Apr 28, 2026
cf6efa6
Add frontend cleanup plan
niklas-westman Apr 28, 2026
5885a3e
Refactor app shell into page components
niklas-westman Apr 29, 2026
23cbc24
Extract shared modal and service section pieces
niklas-westman Apr 29, 2026
4f17897
Split case section into focused components
niklas-westman Apr 29, 2026
1170db3
Split family section into focused components
niklas-westman Apr 29, 2026
b31e1bb
Split contact block into info and form components
niklas-westman Apr 29, 2026
660ae2f
Split industry showcase rendering components
niklas-westman Apr 29, 2026
f3938fb
Extract industry showcase effects into hooks
niklas-westman Apr 29, 2026
8160c7d
Split hero content from canvas shell
niklas-westman Apr 29, 2026
12ab746
Isolate hero canvas animation
niklas-westman Apr 29, 2026
3374b03
Split navigation action controls
niklas-westman Apr 29, 2026
b647a40
Split navigation panel markup
niklas-westman Apr 29, 2026
a571053
Extract navigation entrance hook
niklas-westman Apr 29, 2026
eacb7e0
Extract navigation menu lock hook
niklas-westman Apr 29, 2026
e9ea013
Extract navigation scrollspy hook
niklas-westman Apr 29, 2026
2f56756
Document custom hook purposes
niklas-westman Apr 30, 2026
e24f22c
Fix hero primary button hover color
niklas-westman Apr 30, 2026
9531b70
Slow down ticker animation
niklas-westman Apr 30, 2026
409754d
Fix hero secondary button hover contrast
niklas-westman Apr 30, 2026
53b7b6c
Format hero intro as numbered points
niklas-westman Apr 30, 2026
26a1368
Center hero intro item alignment
niklas-westman Apr 30, 2026
d83573a
Update startups service card copy
niklas-westman Apr 30, 2026
f565237
Use ingress style for service cards
niklas-westman Apr 30, 2026
602beb3
Use ingress style for case cards
niklas-westman Apr 30, 2026
32a185e
Restrict Pages workflow push trigger to main
niklas-westman Apr 30, 2026
5b37bf4
Add alt text to case card images
niklas-westman Apr 30, 2026
815f11c
Improve industry card focus accessibility
niklas-westman Apr 30, 2026
a858075
Improve family tabs focus accessibility
niklas-westman Apr 30, 2026
18e6ffc
Improve public repo README
niklas-westman Apr 30, 2026
27380ab
Remove cleanup planning document
niklas-westman Apr 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 0 additions & 31 deletions .eslintrc.cjs

This file was deleted.

100 changes: 49 additions & 51 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -1,56 +1,54 @@
name: GitHub Pages

on:
push:
branches:
- main
pull_request:
push:
branches:
- main
pull_request:

jobs:
build:
runs-on: ubuntu-22.04
permissions:
contents: write
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
steps:
- uses: actions/checkout@v4

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "20"

- name: Cache dependencies
uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-

- run: npm ci
- run: npm run build

- name: Upload Artifact
uses: actions/upload-pages-artifact@v3
with:
name: github-pages
path: "dist"

deploy:
needs: build
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- name: Configure Pages
uses: actions/configure-pages@v4
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
build:
runs-on: ubuntu-22.04
permissions:
contents: read
env:
VITE_CONTACT_ENDPOINT: ${{ vars.VITE_CONTACT_ENDPOINT }}
steps:
- uses: actions/checkout@v4

- uses: pnpm/action-setup@v4
with:
version: 10

- uses: actions/setup-node@v4
with:
node-version: '20'
cache: pnpm

- run: pnpm install --frozen-lockfile
- run: pnpm build
- run: pnpm test

- name: Upload Pages artifact
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
uses: actions/upload-pages-artifact@v3
with:
path: dist

deploy:
needs: build
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Configure Pages
uses: actions/configure-pages@v5

- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
28 changes: 7 additions & 21 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,24 +1,10 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
coverage
.vite
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
.env
.env.*
*.tsbuildinfo
cdk.out
.pnpm-store
10 changes: 0 additions & 10 deletions .stylelintrc.json

This file was deleted.

87 changes: 86 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,88 @@
<p align="center">
<a href="https://www.wearesprout.se">
<img src="public/sprout-black-name.svg" alt="Sprout" width="320" />
</a>
</p>

# Sprout Technology

[www.wearesprout.se](http://www.wearesprout.se)
Public frontend source for [www.wearesprout.se](https://www.wearesprout.se).

Sprout is a hybrid between an IT consultancy company and an investment company. We build software, back people, and help companies make better technical decisions early enough for those decisions to matter.

Our mission is lasting impact through software: for our clients, for the companies we invest in, and for the people building products that need to work in the real world.

## What We Do

Sprout works across consultancy, software development, AI and agents, advisory, project delivery, startup building, and investment.

The common thread is practical ownership. We help teams modernize legacy systems, build new products, improve architecture, ship internal tools, and bring senior engineering judgment into the moments where quality, speed, and long-term maintainability all matter.

## What We Care About

Software is the tool. Outcomes are the point.

We care about systems that hold up in production, code the next person can understand, teams that take responsibility from idea to launch, and advice shaped by real experience rather than slide-deck theory.

Over the years, Sprout has also grown into a family of companies across consultancy, health, proptech, femtech, fintech, sportstech, and beyond. Those companies are connected by a shared belief that software, used well, builds something lasting.

## Repository

This repository contains the public static frontend for the Sprout website.

- Framework: React with TypeScript
- Build tool: Vite
- Styling: styled-components
- Tests: Vitest and Testing Library
- Hosting: GitHub Pages
- Production domain: `https://www.wearesprout.se`

AWS infrastructure lives in the private ops repository and currently owns the contact API only.

## Development

Install dependencies:

```bash
pnpm install
```

Start the local development server:

```bash
pnpm dev
```

## Validation

Run the production build:

```bash
pnpm build
```

Run the test suite:

```bash
pnpm test
```

## Contact API

The contact form reads `VITE_CONTACT_ENDPOINT` at build time and falls back to `/api/contact` for local or proxied environments.

For GitHub Pages production builds, set this repository variable:

```text
VITE_CONTACT_ENDPOINT=https://<api-id>.execute-api.eu-north-1.amazonaws.com/api/contact
```

This value is public client-side configuration, not a secret.

## Deployment

The GitHub Pages workflow builds and tests pull requests, but only deploys on push to `main`.

After a merge to `main`, GitHub Actions builds `dist` and publishes it to GitHub Pages for the configured custom domain.

The SEO generator uses `https://www.wearesprout.se` as the canonical production origin and generates route entrypoints, metadata, `robots.txt`, and `sitemap.xml`.
45 changes: 45 additions & 0 deletions en/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#0a0a08" />
<meta name="robots" content="index,follow,max-image-preview:large" />
<title>Sprout — Software Developers, Consultants &amp; Investment Company</title>
<meta name="description" content="Sprout builds lasting impact through software as a software developer, consultancy, and investment company." />
<link rel="canonical" href="https://www.wearesprout.se/en/" />
<link rel="alternate" hreflang="en" href="https://www.wearesprout.se/en/" />
<link rel="alternate" hreflang="sv" href="https://www.wearesprout.se/sv/" />
<link rel="alternate" hreflang="x-default" href="https://www.wearesprout.se/en/" />
<link rel="icon" href="/favicon.ico" sizes="any" />
<link rel="apple-touch-icon" href="/sprout-white-symbol.png" />
<meta property="og:type" content="website" />
<meta property="og:site_name" content="Sprout" />
<meta property="og:title" content="Sprout — Software Developers, Consultants &amp; Investment Company" />
<meta property="og:description" content="Sprout builds lasting impact through software as a software developer, consultancy, and investment company." />
<meta property="og:url" content="https://www.wearesprout.se/en/" />
<meta property="og:image" content="https://www.wearesprout.se/og-image.png" />
<meta property="og:image:alt" content="Sprout logo" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Sprout — Software Developers, Consultants &amp; Investment Company" />
<meta name="twitter:description" content="Sprout builds lasting impact through software as a software developer, consultancy, and investment company." />
<meta name="twitter:image" content="https://www.wearesprout.se/og-image.png" />
<meta name="twitter:image:alt" content="Sprout logo" />
<script type="application/ld+json">
{"@context":"https://schema.org","@type":"Organization","name":"Sprout","url":"https://www.wearesprout.se","logo":"https://www.wearesprout.se/og-image.png","email":"hello@wearesprout.se"}
</script>
<script type="application/ld+json">
{"@context":"https://schema.org","@type":"WebSite","name":"Sprout","url":"https://www.wearesprout.se/en/","description":"Sprout builds lasting impact through software as a software developer, consultancy, and investment company.","inLanguage":"en"}
</script>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;700&display=swap"
rel="stylesheet"
/>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
45 changes: 45 additions & 0 deletions en/services/advisory/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#0a0a08" />
<meta name="robots" content="index,follow,max-image-preview:large" />
<title>Advisory — Sprout — Software Developers, Consultants &amp; Investment Company</title>
<meta name="description" content="We&#39;ve made enough mistakes to give genuinely useful advice. That&#39;s not false modesty, it&#39;s just true. We&#39;ve been around long enough to have seen most of the traps up close, a few of them from the inside. When you ask us what we think, we&#39;ll tell you what we actually think. The good, the awkward, and the &#39;you might not want to hear this but.&#39; We&#39;re not here to validate a decision you&#39;ve already made. We&#39;re here to help you make a better one. Sometimes those are the same thing. When they&#39;re not — we&#39;ll say so." />
<link rel="canonical" href="https://www.wearesprout.se/en/services/advisory/" />
<link rel="alternate" hreflang="en" href="https://www.wearesprout.se/en/services/advisory/" />
<link rel="alternate" hreflang="sv" href="https://www.wearesprout.se/sv/tjanster/radgivning/" />
<link rel="alternate" hreflang="x-default" href="https://www.wearesprout.se/en/" />
<link rel="icon" href="/favicon.ico" sizes="any" />
<link rel="apple-touch-icon" href="/sprout-white-symbol.png" />
<meta property="og:type" content="website" />
<meta property="og:site_name" content="Sprout" />
<meta property="og:title" content="Advisory — Sprout — Software Developers, Consultants &amp; Investment Company" />
<meta property="og:description" content="We&#39;ve made enough mistakes to give genuinely useful advice. That&#39;s not false modesty, it&#39;s just true. We&#39;ve been around long enough to have seen most of the traps up close, a few of them from the inside. When you ask us what we think, we&#39;ll tell you what we actually think. The good, the awkward, and the &#39;you might not want to hear this but.&#39; We&#39;re not here to validate a decision you&#39;ve already made. We&#39;re here to help you make a better one. Sometimes those are the same thing. When they&#39;re not — we&#39;ll say so." />
<meta property="og:url" content="https://www.wearesprout.se/en/services/advisory/" />
<meta property="og:image" content="https://www.wearesprout.se/og-image.png" />
<meta property="og:image:alt" content="Sprout logo" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Advisory — Sprout — Software Developers, Consultants &amp; Investment Company" />
<meta name="twitter:description" content="We&#39;ve made enough mistakes to give genuinely useful advice. That&#39;s not false modesty, it&#39;s just true. We&#39;ve been around long enough to have seen most of the traps up close, a few of them from the inside. When you ask us what we think, we&#39;ll tell you what we actually think. The good, the awkward, and the &#39;you might not want to hear this but.&#39; We&#39;re not here to validate a decision you&#39;ve already made. We&#39;re here to help you make a better one. Sometimes those are the same thing. When they&#39;re not — we&#39;ll say so." />
<meta name="twitter:image" content="https://www.wearesprout.se/og-image.png" />
<meta name="twitter:image:alt" content="Sprout logo" />
<script type="application/ld+json">
{"@context":"https://schema.org","@type":"Organization","name":"Sprout","url":"https://www.wearesprout.se","logo":"https://www.wearesprout.se/og-image.png","email":"hello@wearesprout.se"}
</script>
<script type="application/ld+json">
{"@context":"https://schema.org","@type":"Service","name":"Advisory","url":"https://www.wearesprout.se/en/services/advisory/","description":"We've made enough mistakes to give genuinely useful advice. That's not false modesty, it's just true. We've been around long enough to have seen most of the traps up close, a few of them from the inside. When you ask us what we think, we'll tell you what we actually think. The good, the awkward, and the 'you might not want to hear this but.' We're not here to validate a decision you've already made. We're here to help you make a better one. Sometimes those are the same thing. When they're not — we'll say so.","inLanguage":"en"}
</script>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;700&display=swap"
rel="stylesheet"
/>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
Loading
Loading