From 23f88bb433606c42bf206ecfaffae84afcb8a82a Mon Sep 17 00:00:00 2001 From: Manpreet Singh <166604315+Code-311@users.noreply.github.com> Date: Tue, 10 Mar 2026 11:43:59 +0530 Subject: [PATCH] Remove accent switching and integrate Logo-Out brand mark --- .github/workflows/ci.yml | 88 +++++++ .github/workflows/release.yml | 67 +++++ .gitignore | 50 ++++ Controllers/HomeController.cs | 179 ++++++++++++++ Models/MediaItem.cs | 9 + Models/ModelItem.cs | 9 + Models/ToolItem.cs | 8 + Models/WritingItem.cs | 10 + Program.cs | 24 ++ README.md | 41 ++++ ViewModels/ContentDetailViewModel.cs | 9 + ViewModels/HomePageViewModel.cs | 17 ++ ViewModels/MediaPageViewModel.cs | 10 + ViewModels/ModelsPageViewModel.cs | 10 + ViewModels/SimplePageViewModel.cs | 8 + ViewModels/ToolsPageViewModel.cs | 10 + ViewModels/WritingsPageViewModel.cs | 10 + Views/Home/About.cshtml | 17 ++ Views/Home/Contact.cshtml | 17 ++ Views/Home/ContentDetail.cshtml | 17 ++ Views/Home/Index.cshtml | 13 + Views/Home/Media.cshtml | 24 ++ Views/Home/Models.cshtml | 21 ++ Views/Home/Tools.cshtml | 20 ++ Views/Home/Writings.cshtml | 24 ++ Views/Shared/_AboutPreview.cshtml | 9 + Views/Shared/_ContactPreview.cshtml | 9 + Views/Shared/_FeaturedModels.cshtml | 17 ++ Views/Shared/_Footer.cshtml | 3 + Views/Shared/_Header.cshtml | 22 ++ Views/Shared/_Hero.cshtml | 11 + Views/Shared/_Layout.cshtml | 18 ++ Views/Shared/_MediaPreview.cshtml | 19 ++ Views/Shared/_SelectedWritings.cshtml | 20 ++ Views/Shared/_ToolsPreview.cshtml | 16 ++ Views/Shared/_WhyGapIntro.cshtml | 14 ++ Views/_ViewImports.cshtml | 4 + Views/_ViewStart.cshtml | 3 + manpreetsingh.pro.csproj | 7 + wwwroot/css/site.css | 340 ++++++++++++++++++++++++++ wwwroot/img/Logo-Out.svg | 4 + wwwroot/img/placeholder-1.svg | 7 + wwwroot/img/placeholder-2.svg | 6 + wwwroot/img/placeholder-3.svg | 6 + wwwroot/img/placeholder-4.svg | 6 + wwwroot/js/site.js | 36 +++ 46 files changed, 1289 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/release.yml create mode 100644 .gitignore create mode 100644 Controllers/HomeController.cs create mode 100644 Models/MediaItem.cs create mode 100644 Models/ModelItem.cs create mode 100644 Models/ToolItem.cs create mode 100644 Models/WritingItem.cs create mode 100644 Program.cs create mode 100644 README.md create mode 100644 ViewModels/ContentDetailViewModel.cs create mode 100644 ViewModels/HomePageViewModel.cs create mode 100644 ViewModels/MediaPageViewModel.cs create mode 100644 ViewModels/ModelsPageViewModel.cs create mode 100644 ViewModels/SimplePageViewModel.cs create mode 100644 ViewModels/ToolsPageViewModel.cs create mode 100644 ViewModels/WritingsPageViewModel.cs create mode 100644 Views/Home/About.cshtml create mode 100644 Views/Home/Contact.cshtml create mode 100644 Views/Home/ContentDetail.cshtml create mode 100644 Views/Home/Index.cshtml create mode 100644 Views/Home/Media.cshtml create mode 100644 Views/Home/Models.cshtml create mode 100644 Views/Home/Tools.cshtml create mode 100644 Views/Home/Writings.cshtml create mode 100644 Views/Shared/_AboutPreview.cshtml create mode 100644 Views/Shared/_ContactPreview.cshtml create mode 100644 Views/Shared/_FeaturedModels.cshtml create mode 100644 Views/Shared/_Footer.cshtml create mode 100644 Views/Shared/_Header.cshtml create mode 100644 Views/Shared/_Hero.cshtml create mode 100644 Views/Shared/_Layout.cshtml create mode 100644 Views/Shared/_MediaPreview.cshtml create mode 100644 Views/Shared/_SelectedWritings.cshtml create mode 100644 Views/Shared/_ToolsPreview.cshtml create mode 100644 Views/Shared/_WhyGapIntro.cshtml create mode 100644 Views/_ViewImports.cshtml create mode 100644 Views/_ViewStart.cshtml create mode 100644 manpreetsingh.pro.csproj create mode 100644 wwwroot/css/site.css create mode 100644 wwwroot/img/Logo-Out.svg create mode 100644 wwwroot/img/placeholder-1.svg create mode 100644 wwwroot/img/placeholder-2.svg create mode 100644 wwwroot/img/placeholder-3.svg create mode 100644 wwwroot/img/placeholder-4.svg create mode 100644 wwwroot/js/site.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..7ac6e57 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,88 @@ +name: ci + +on: + push: + branches: + - main + pull_request: + branches: + - main + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build-and-test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Resolve .NET SDK version + id: dotnet-version + shell: bash + run: | + if [ -f global.json ]; then + VERSION=$(python - <<'PY' +import json +with open('global.json', 'r', encoding='utf-8') as f: + print(json.load(f)['sdk']['version']) +PY +) + else + VERSION=$(python - <<'PY' +import re +from pathlib import Path +csproj = next(Path('.').glob('*.csproj')) +text = csproj.read_text(encoding='utf-8') +m = re.search(r'net(\d+)\.(\d+)', text) +if not m: + raise SystemExit('Unable to resolve TargetFramework from project file.') +print(f"{m.group(1)}.{m.group(2)}.x") +PY +) + fi + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ steps.dotnet-version.outputs.version }} + + - name: Restore + run: dotnet restore manpreetsingh.pro.csproj + + - name: Build (Release) + run: dotnet build manpreetsingh.pro.csproj --configuration Release --no-restore + + - name: Discover test projects + id: test-projects + shell: bash + run: | + { + echo "projects<> "$GITHUB_OUTPUT" + + - name: Run tests (Release) + if: steps.test-projects.outputs.projects != '' + shell: bash + run: | + while IFS= read -r test_proj; do + [ -z "$test_proj" ] && continue + dotnet test "$test_proj" --configuration Release --no-build + done <<< "${{ steps.test-projects.outputs.projects }}" + + - name: No test projects found + if: steps.test-projects.outputs.projects == '' + run: echo "No test projects found. Skipping test execution." diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..e44c602 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,67 @@ +name: release + +on: + push: + tags: + - 'v*' + workflow_dispatch: + +permissions: + contents: read + +jobs: + publish: + runs-on: ubuntu-latest + env: + WEB_PROJECT: manpreetsingh.pro.csproj + PUBLISH_DIR: artifacts/publish + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Resolve .NET SDK version + id: dotnet-version + shell: bash + run: | + if [ -f global.json ]; then + VERSION=$(python - <<'PY' +import json +with open('global.json', 'r', encoding='utf-8') as f: + print(json.load(f)['sdk']['version']) +PY +) + else + VERSION=$(python - <<'PY' +import re +from pathlib import Path +csproj = next(Path('.').glob('*.csproj')) +text = csproj.read_text(encoding='utf-8') +m = re.search(r'net(\d+)\.(\d+)', text) +if not m: + raise SystemExit('Unable to resolve TargetFramework from project file.') +print(f"{m.group(1)}.{m.group(2)}.x") +PY +) + fi + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ steps.dotnet-version.outputs.version }} + + - name: Restore + run: dotnet restore ${{ env.WEB_PROJECT }} + + - name: Build (Release) + run: dotnet build ${{ env.WEB_PROJECT }} --configuration Release --no-restore + + - name: Publish + run: dotnet publish ${{ env.WEB_PROJECT }} --configuration Release --no-build --output ${{ env.PUBLISH_DIR }} + + - name: Upload publish artifact + uses: actions/upload-artifact@v4 + with: + name: manpreetsingh-pro-${{ github.ref_name }} + path: ${{ env.PUBLISH_DIR }} + if-no-files-found: error diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..00fdc0a --- /dev/null +++ b/.gitignore @@ -0,0 +1,50 @@ +# Build artifacts +bin/ +obj/ +out/ +publish/ +artifacts/ + +# IDE and tooling +.vs/ +.vscode/ +.idea/ +*.user +*.suo +*.userosscache +*.sln.docstates + +# Logs and diagnostics +*.log +*.tlog +TestResults/ + +# NuGet +*.nupkg +*.snupkg +packages/ + +# Compiled binaries/libraries +*.dll +*.exe +*.pdb +*.so +*.dylib +*.a +*.lib + +# Packaging and archives +*.zip +*.7z +*.rar +*.tar +*.gz + +# Generated frontend artifacts +node_modules/ +dist/ +coverage/ + +# OS files +.DS_Store +Thumbs.db diff --git a/Controllers/HomeController.cs b/Controllers/HomeController.cs new file mode 100644 index 0000000..07af4de --- /dev/null +++ b/Controllers/HomeController.cs @@ -0,0 +1,179 @@ +using manpreetsingh.pro.Models; +using manpreetsingh.pro.ViewModels; +using Microsoft.AspNetCore.Mvc; + +namespace manpreetsingh.pro.Controllers; + +public class HomeController : Controller +{ + [HttpGet("/")] + public IActionResult Index() + { + var model = new HomePageViewModel + { + HeroTitle = "HOW ORGANIZATIONS REALLY WORK", + HeroSubtext = "Understanding the forces behind decisions, hierarchy, delay, and execution.", + WhyGapIntro = "Most organizational behavior looks personal from the outside but structural from the inside.", + WhyGapPoints = + [ + "Leaders often act inside constraints they rarely state out loud.", + "Managers absorb the consequences without visibility into those constraints.", + "That distance between decision logic and lived reality is the Why Gap." + ], + FeaturedModels = GetModels(), + SelectedWritings = GetWritings(), + Tools = GetTools(), + MediaItems = GetMedia(), + AboutSummary = "Manpreet Singh writes and teaches about how real systems shape real behavior inside complex organizations.", + ContactSummary = "For speaking, advisory work, and private workshops focused on operating reality, reach out directly." + }; + + return View(model); + } + + [HttpGet("/writings")] + public IActionResult Writings() => View(new WritingsPageViewModel + { + Intro = "Essays for middle and senior managers trying to make sense of pressure, delay, and institutional behavior.", + Items = GetWritings() + }); + + [HttpGet("/writings/{slug}")] + public IActionResult Writing(string slug) + { + var item = GetWritings().FirstOrDefault(x => x.Slug == slug); + if (item is null) + { + return NotFound(); + } + + return View("ContentDetail", new ContentDetailViewModel + { + SectionLabel = "Writing", + Title = item.Title, + Summary = item.Summary, + Points = + [ + "Where decision rights actually sit is usually different from the org chart.", + "Most slowdowns are produced by risk routing, not individual resistance.", + "Repair starts with naming the system pressure before naming the people." + ] + }); + } + + [HttpGet("/models")] + public IActionResult Models() => View(new ModelsPageViewModel + { + Intro = "Simple explanatory models for diagnosing recurring organizational patterns.", + Items = GetModels() + }); + + [HttpGet("/models/{slug}")] + public IActionResult Model(string slug) + { + var item = GetModels().FirstOrDefault(x => x.Slug == slug); + if (item is null) + { + return NotFound(); + } + + return View("ContentDetail", new ContentDetailViewModel + { + SectionLabel = "Model", + Title = item.Name, + Summary = item.Summary, + Points = [item.CoreQuestion, "Use this model before prescribing behavior changes."] + }); + } + + [HttpGet("/tools")] + public IActionResult Tools() => View(new ToolsPageViewModel + { + Intro = "Practical checks to apply the models in real operating contexts.", + Items = GetTools() + }); + + [HttpGet("/tools/{slug}")] + public IActionResult Tool(string slug) + { + var item = GetTools().FirstOrDefault(x => x.Slug == slug); + if (item is null) + { + return NotFound(); + } + + return View("ContentDetail", new ContentDetailViewModel + { + SectionLabel = "Tool", + Title = item.Name, + Summary = item.Summary, + Points = + [ + "Use in team reviews, operating retros, or planning sessions.", + "Focus on constraints, sequencing, and decision visibility." + ] + }); + } + + [HttpGet("/media")] + public IActionResult Media() => View(new MediaPageViewModel + { + Intro = "Short talks and video explanations for teams that need shared language quickly.", + Items = GetMedia() + }); + + [HttpGet("/about")] + public IActionResult About() => View(new SimplePageViewModel + { + Title = "About", + Intro = "This platform exists to explain why complex organizations behave the way they do.", + Details = + [ + "Audience: middle and senior managers.", + "Method: plain language, structural analysis, practical models.", + "Tone: calm, direct, non-jargon." + ] + }); + + [HttpGet("/contact")] + public IActionResult Contact() => View(new SimplePageViewModel + { + Title = "Contact", + Intro = "For speaking, advisory work, and workshops.", + Details = + [ + "Email: hello@manpreetsingh.pro", + "Location: Global / Remote", + "Response window: 3–5 business days" + ] + }); + + private static List GetModels() => + [ + new() { Name = "The Why Gap", Slug = "the-why-gap", Summary = "Explains the distance between visible decisions and invisible constraints.", CoreQuestion = "What constraint is driving this decision that frontline managers cannot see?" }, + new() { Name = "The Friction Map", Slug = "the-friction-map", Summary = "Surfaces where work slows, who absorbs cost, and where accountability breaks.", CoreQuestion = "Where does work stall repeatedly, and who carries the hidden load?" }, + new() { Name = "The Visibility Trap", Slug = "the-visibility-trap", Summary = "Shows how reporting systems reward legibility over reality.", CoreQuestion = "What appears healthy in dashboards but fails in daily execution?" }, + new() { Name = "Quiet Erosion", Slug = "quiet-erosion", Summary = "Describes how standards decay gradually under sustained pressure.", CoreQuestion = "Which standard is being quietly traded away to keep throughput?" } + ]; + + private static List GetWritings() => + [ + new() { Title = "The Why Gap Inside Organizations", Slug = "the-why-gap-inside-organizations", Summary = "Why decision logic becomes opaque as hierarchy grows.", PublishedOn = "2026-02-10", ReadTime = "8 min" }, + new() { Title = "Why Large Organizations Move Slowly", Slug = "why-large-organizations-move-slowly", Summary = "Delay is usually a design property, not a motivation problem.", PublishedOn = "2026-01-21", ReadTime = "7 min" }, + new() { Title = "Why Leaders Centralize Decisions", Slug = "why-leaders-centralize-decisions", Summary = "Centralization often signals risk concentration, not ego.", PublishedOn = "2025-12-18", ReadTime = "6 min" }, + new() { Title = "Why Good Managers Get Trapped in Bad Systems", Slug = "why-good-managers-get-trapped-in-bad-systems", Summary = "Competent people still fail in misaligned structures.", PublishedOn = "2025-11-09", ReadTime = "9 min" }, + new() { Title = "How Standards Erode Quietly", Slug = "how-standards-erode-quietly", Summary = "How small exceptions normalize under pressure.", PublishedOn = "2025-10-03", ReadTime = "5 min" } + ]; + + private static List GetTools() => + [ + new() { Name = "Why Gap Check", Slug = "why-gap-check", Summary = "A guided prompt set to expose hidden decision constraints." }, + new() { Name = "Friction Map Check", Slug = "friction-map-check", Summary = "A simple mapping exercise for recurring delays and handoff failures." } + ]; + + private static List GetMedia() => + [ + new() { Title = "Why organizations stall even when everyone is busy", Format = "Talk", Duration = "22 min", Summary = "A practical explanation of delay mechanics across layers." }, + new() { Title = "The Why Gap in executive communication", Format = "Video", Duration = "14 min", Summary = "How language hides constraints and distorts alignment." } + ]; +} diff --git a/Models/MediaItem.cs b/Models/MediaItem.cs new file mode 100644 index 0000000..07c5abc --- /dev/null +++ b/Models/MediaItem.cs @@ -0,0 +1,9 @@ +namespace manpreetsingh.pro.Models; + +public class MediaItem +{ + public string Title { get; set; } = string.Empty; + public string Format { get; set; } = string.Empty; + public string Duration { get; set; } = string.Empty; + public string Summary { get; set; } = string.Empty; +} diff --git a/Models/ModelItem.cs b/Models/ModelItem.cs new file mode 100644 index 0000000..af9b905 --- /dev/null +++ b/Models/ModelItem.cs @@ -0,0 +1,9 @@ +namespace manpreetsingh.pro.Models; + +public class ModelItem +{ + public string Name { get; set; } = string.Empty; + public string Slug { get; set; } = string.Empty; + public string Summary { get; set; } = string.Empty; + public string CoreQuestion { get; set; } = string.Empty; +} diff --git a/Models/ToolItem.cs b/Models/ToolItem.cs new file mode 100644 index 0000000..b145728 --- /dev/null +++ b/Models/ToolItem.cs @@ -0,0 +1,8 @@ +namespace manpreetsingh.pro.Models; + +public class ToolItem +{ + public string Name { get; set; } = string.Empty; + public string Slug { get; set; } = string.Empty; + public string Summary { get; set; } = string.Empty; +} diff --git a/Models/WritingItem.cs b/Models/WritingItem.cs new file mode 100644 index 0000000..35f44e7 --- /dev/null +++ b/Models/WritingItem.cs @@ -0,0 +1,10 @@ +namespace manpreetsingh.pro.Models; + +public class WritingItem +{ + public string Title { get; set; } = string.Empty; + public string Slug { get; set; } = string.Empty; + public string Summary { get; set; } = string.Empty; + public string PublishedOn { get; set; } = string.Empty; + public string ReadTime { get; set; } = string.Empty; +} diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..ed90792 --- /dev/null +++ b/Program.cs @@ -0,0 +1,24 @@ +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddControllersWithViews(); + +var app = builder.Build(); + +if (!app.Environment.IsDevelopment()) +{ + app.UseExceptionHandler("/Home/Error"); + app.UseHsts(); +} + +app.UseHttpsRedirection(); +app.UseStaticFiles(); + +app.UseRouting(); + +app.UseAuthorization(); + +app.MapControllerRoute( + name: "default", + pattern: "{controller=Home}/{action=Index}/{id?}"); + +app.Run(); diff --git a/README.md b/README.md new file mode 100644 index 0000000..1a64e53 --- /dev/null +++ b/README.md @@ -0,0 +1,41 @@ +# Manpreet Singh — How Organizations Really Work + +An ASP.NET Core MVC (.NET 8) website focused on explaining why complex organizations behave the way they do. + +## Stack +- ASP.NET Core MVC + Razor Views +- Plain CSS (design tokens + responsive layout) +- Vanilla JavaScript (theme control + restrained reveal motion) + +## Sitemap +- / +- /writings +- /writings/{slug} +- /models +- /models/{slug} +- /tools +- /tools/{slug} +- /media +- /about +- /contact + +## Run locally +```bash +dotnet restore +dotnet run +``` + +## UI behavior +- System color mode is respected by default (`prefers-color-scheme`). +- Users can override light/dark mode in the header; preference is saved in `localStorage`. +- Motion is subtle and respects `prefers-reduced-motion`. + +## CI/CD +- `ci` workflow runs on pushes to `main`, pull requests to `main`, and manual dispatch. +- `release` workflow runs on `v*` tags and manual dispatch, publishes to `artifacts/publish`, and uploads that directory as the release artifact. +- To trigger a tagged release artifact: + +```bash +git tag v0.1.0 +git push origin v0.1.0 +``` diff --git a/ViewModels/ContentDetailViewModel.cs b/ViewModels/ContentDetailViewModel.cs new file mode 100644 index 0000000..6a53c1e --- /dev/null +++ b/ViewModels/ContentDetailViewModel.cs @@ -0,0 +1,9 @@ +namespace manpreetsingh.pro.ViewModels; + +public class ContentDetailViewModel +{ + public string SectionLabel { get; set; } = string.Empty; + public string Title { get; set; } = string.Empty; + public string Summary { get; set; } = string.Empty; + public IReadOnlyList Points { get; set; } = []; +} diff --git a/ViewModels/HomePageViewModel.cs b/ViewModels/HomePageViewModel.cs new file mode 100644 index 0000000..0782112 --- /dev/null +++ b/ViewModels/HomePageViewModel.cs @@ -0,0 +1,17 @@ +using manpreetsingh.pro.Models; + +namespace manpreetsingh.pro.ViewModels; + +public class HomePageViewModel +{ + public string HeroTitle { get; set; } = string.Empty; + public string HeroSubtext { get; set; } = string.Empty; + public string WhyGapIntro { get; set; } = string.Empty; + public IReadOnlyList WhyGapPoints { get; set; } = []; + public IReadOnlyList FeaturedModels { get; set; } = []; + public IReadOnlyList SelectedWritings { get; set; } = []; + public IReadOnlyList Tools { get; set; } = []; + public IReadOnlyList MediaItems { get; set; } = []; + public string AboutSummary { get; set; } = string.Empty; + public string ContactSummary { get; set; } = string.Empty; +} diff --git a/ViewModels/MediaPageViewModel.cs b/ViewModels/MediaPageViewModel.cs new file mode 100644 index 0000000..33df9cf --- /dev/null +++ b/ViewModels/MediaPageViewModel.cs @@ -0,0 +1,10 @@ +using manpreetsingh.pro.Models; + +namespace manpreetsingh.pro.ViewModels; + +public class MediaPageViewModel +{ + public string Title { get; set; } = "Media"; + public string Intro { get; set; } = string.Empty; + public IReadOnlyList Items { get; set; } = []; +} diff --git a/ViewModels/ModelsPageViewModel.cs b/ViewModels/ModelsPageViewModel.cs new file mode 100644 index 0000000..3d39913 --- /dev/null +++ b/ViewModels/ModelsPageViewModel.cs @@ -0,0 +1,10 @@ +using manpreetsingh.pro.Models; + +namespace manpreetsingh.pro.ViewModels; + +public class ModelsPageViewModel +{ + public string Title { get; set; } = "Models"; + public string Intro { get; set; } = string.Empty; + public IReadOnlyList Items { get; set; } = []; +} diff --git a/ViewModels/SimplePageViewModel.cs b/ViewModels/SimplePageViewModel.cs new file mode 100644 index 0000000..7aae050 --- /dev/null +++ b/ViewModels/SimplePageViewModel.cs @@ -0,0 +1,8 @@ +namespace manpreetsingh.pro.ViewModels; + +public class SimplePageViewModel +{ + public string Title { get; set; } = string.Empty; + public string Intro { get; set; } = string.Empty; + public IReadOnlyList Details { get; set; } = []; +} diff --git a/ViewModels/ToolsPageViewModel.cs b/ViewModels/ToolsPageViewModel.cs new file mode 100644 index 0000000..a733ce3 --- /dev/null +++ b/ViewModels/ToolsPageViewModel.cs @@ -0,0 +1,10 @@ +using manpreetsingh.pro.Models; + +namespace manpreetsingh.pro.ViewModels; + +public class ToolsPageViewModel +{ + public string Title { get; set; } = "Tools"; + public string Intro { get; set; } = string.Empty; + public IReadOnlyList Items { get; set; } = []; +} diff --git a/ViewModels/WritingsPageViewModel.cs b/ViewModels/WritingsPageViewModel.cs new file mode 100644 index 0000000..f40d2ba --- /dev/null +++ b/ViewModels/WritingsPageViewModel.cs @@ -0,0 +1,10 @@ +using manpreetsingh.pro.Models; + +namespace manpreetsingh.pro.ViewModels; + +public class WritingsPageViewModel +{ + public string Title { get; set; } = "Writings"; + public string Intro { get; set; } = string.Empty; + public IReadOnlyList Items { get; set; } = []; +} diff --git a/Views/Home/About.cshtml b/Views/Home/About.cshtml new file mode 100644 index 0000000..408fa4d --- /dev/null +++ b/Views/Home/About.cshtml @@ -0,0 +1,17 @@ +@model SimplePageViewModel +@{ + ViewData["Title"] = Model.Title; +} +
+

About

+

@Model.Title

+

@Model.Intro

+
+
+
    + @foreach (var line in Model.Details) + { +
  • @line
  • + } +
+
diff --git a/Views/Home/Contact.cshtml b/Views/Home/Contact.cshtml new file mode 100644 index 0000000..d79ab7e --- /dev/null +++ b/Views/Home/Contact.cshtml @@ -0,0 +1,17 @@ +@model SimplePageViewModel +@{ + ViewData["Title"] = Model.Title; +} +
+

Contact

+

@Model.Title

+

@Model.Intro

+
+
+
    + @foreach (var line in Model.Details) + { +
  • @line
  • + } +
+
diff --git a/Views/Home/ContentDetail.cshtml b/Views/Home/ContentDetail.cshtml new file mode 100644 index 0000000..9a7f1fa --- /dev/null +++ b/Views/Home/ContentDetail.cshtml @@ -0,0 +1,17 @@ +@model ContentDetailViewModel +@{ + ViewData["Title"] = Model.Title; +} +
+

@Model.SectionLabel

+

@Model.Title

+

@Model.Summary

+
+
+
    + @foreach (var point in Model.Points) + { +
  • @point
  • + } +
+
diff --git a/Views/Home/Index.cshtml b/Views/Home/Index.cshtml new file mode 100644 index 0000000..9c60b6a --- /dev/null +++ b/Views/Home/Index.cshtml @@ -0,0 +1,13 @@ +@model HomePageViewModel +@{ + ViewData["Title"] = "Home"; +} + +@await Html.PartialAsync("_Hero", Model) +@await Html.PartialAsync("_WhyGapIntro", Model) +@await Html.PartialAsync("_FeaturedModels", Model) +@await Html.PartialAsync("_SelectedWritings", Model) +@await Html.PartialAsync("_ToolsPreview", Model) +@await Html.PartialAsync("_MediaPreview", Model) +@await Html.PartialAsync("_AboutPreview", Model) +@await Html.PartialAsync("_ContactPreview", Model) diff --git a/Views/Home/Media.cshtml b/Views/Home/Media.cshtml new file mode 100644 index 0000000..da411f8 --- /dev/null +++ b/Views/Home/Media.cshtml @@ -0,0 +1,24 @@ +@model MediaPageViewModel +@{ + ViewData["Title"] = Model.Title; +} +
+

Media

+

@Model.Title

+

@Model.Intro

+
+
+
+ @foreach (var item in Model.Items) + { +
+

@item.Title

+

@item.Summary

+
+ @item.Format + @item.Duration +
+
+ } +
+
diff --git a/Views/Home/Models.cshtml b/Views/Home/Models.cshtml new file mode 100644 index 0000000..3a4121d --- /dev/null +++ b/Views/Home/Models.cshtml @@ -0,0 +1,21 @@ +@model ModelsPageViewModel +@{ + ViewData["Title"] = Model.Title; +} +
+

Models

+

@Model.Title

+

@Model.Intro

+
+
+
+ @foreach (var item in Model.Items) + { +
+

@item.Name

+

@item.Summary

+

@item.CoreQuestion

+
+ } +
+
diff --git a/Views/Home/Tools.cshtml b/Views/Home/Tools.cshtml new file mode 100644 index 0000000..908e1c0 --- /dev/null +++ b/Views/Home/Tools.cshtml @@ -0,0 +1,20 @@ +@model ToolsPageViewModel +@{ + ViewData["Title"] = Model.Title; +} +
+

Tools

+

@Model.Title

+

@Model.Intro

+
+
+
+ @foreach (var item in Model.Items) + { + + } +
+
diff --git a/Views/Home/Writings.cshtml b/Views/Home/Writings.cshtml new file mode 100644 index 0000000..37ccbdc --- /dev/null +++ b/Views/Home/Writings.cshtml @@ -0,0 +1,24 @@ +@model WritingsPageViewModel +@{ + ViewData["Title"] = Model.Title; +} +
+

Writings

+

@Model.Title

+

@Model.Intro

+
+
+
+ @foreach (var item in Model.Items) + { +
+

@item.Title

+

@item.Summary

+
+ @item.PublishedOn + @item.ReadTime +
+
+ } +
+
diff --git a/Views/Shared/_AboutPreview.cshtml b/Views/Shared/_AboutPreview.cshtml new file mode 100644 index 0000000..b10bc9d --- /dev/null +++ b/Views/Shared/_AboutPreview.cshtml @@ -0,0 +1,9 @@ +@model HomePageViewModel +
+
+

06

+

About

+
+

@Model.AboutSummary

+

Read full about

+
diff --git a/Views/Shared/_ContactPreview.cshtml b/Views/Shared/_ContactPreview.cshtml new file mode 100644 index 0000000..f8cb512 --- /dev/null +++ b/Views/Shared/_ContactPreview.cshtml @@ -0,0 +1,9 @@ +@model HomePageViewModel +
+
+

07

+

Contact / engagement

+
+

@Model.ContactSummary

+

Contact details

+
diff --git a/Views/Shared/_FeaturedModels.cshtml b/Views/Shared/_FeaturedModels.cshtml new file mode 100644 index 0000000..e0b2ef6 --- /dev/null +++ b/Views/Shared/_FeaturedModels.cshtml @@ -0,0 +1,17 @@ +@model HomePageViewModel +
+
+

02

+ +
+
+ @foreach (var model in Model.FeaturedModels) + { +
+

@model.Name

+

@model.Summary

+

@model.CoreQuestion

+
+ } +
+
diff --git a/Views/Shared/_Footer.cshtml b/Views/Shared/_Footer.cshtml new file mode 100644 index 0000000..0c45bed --- /dev/null +++ b/Views/Shared/_Footer.cshtml @@ -0,0 +1,3 @@ +
+

Manpreet Singh · How organizations really work.

+
diff --git a/Views/Shared/_Header.cshtml b/Views/Shared/_Header.cshtml new file mode 100644 index 0000000..89916a3 --- /dev/null +++ b/Views/Shared/_Header.cshtml @@ -0,0 +1,22 @@ + diff --git a/Views/Shared/_Hero.cshtml b/Views/Shared/_Hero.cshtml new file mode 100644 index 0000000..d6c1cda --- /dev/null +++ b/Views/Shared/_Hero.cshtml @@ -0,0 +1,11 @@ +@model HomePageViewModel +
+
+ +

manpreet singh

+
+
+

@Model.HeroTitle

+

@Model.HeroSubtext

+
+
diff --git a/Views/Shared/_Layout.cshtml b/Views/Shared/_Layout.cshtml new file mode 100644 index 0000000..db86be4 --- /dev/null +++ b/Views/Shared/_Layout.cshtml @@ -0,0 +1,18 @@ + + + + + + @ViewData["Title"] - Manpreet Singh + + + + + @await Html.PartialAsync("_Header") +
+ @RenderBody() +
+ @await Html.PartialAsync("_Footer") + + + diff --git a/Views/Shared/_MediaPreview.cshtml b/Views/Shared/_MediaPreview.cshtml new file mode 100644 index 0000000..8f20a7d --- /dev/null +++ b/Views/Shared/_MediaPreview.cshtml @@ -0,0 +1,19 @@ +@model HomePageViewModel +
+
+

05

+

Media preview

+
+
+ @foreach (var item in Model.MediaItems) + { + + } +
+
diff --git a/Views/Shared/_SelectedWritings.cshtml b/Views/Shared/_SelectedWritings.cshtml new file mode 100644 index 0000000..fdbd52c --- /dev/null +++ b/Views/Shared/_SelectedWritings.cshtml @@ -0,0 +1,20 @@ +@model HomePageViewModel +
+
+

03

+

Selected writings

+
+
+ @for (var i = 0; i < Math.Min(3, Model.SelectedWritings.Count); i++) + { + var item = Model.SelectedWritings[i]; +
+

@item.Title

+
+ @item.PublishedOn + @item.ReadTime +
+
+ } +
+
diff --git a/Views/Shared/_ToolsPreview.cshtml b/Views/Shared/_ToolsPreview.cshtml new file mode 100644 index 0000000..efe5440 --- /dev/null +++ b/Views/Shared/_ToolsPreview.cshtml @@ -0,0 +1,16 @@ +@model HomePageViewModel +
+
+

04

+

Tools preview

+
+
+ @foreach (var item in Model.Tools) + { + + } +
+
diff --git a/Views/Shared/_WhyGapIntro.cshtml b/Views/Shared/_WhyGapIntro.cshtml new file mode 100644 index 0000000..3fbb4b1 --- /dev/null +++ b/Views/Shared/_WhyGapIntro.cshtml @@ -0,0 +1,14 @@ +@model HomePageViewModel +
+
+

01

+

Why Gap

+
+

@Model.WhyGapIntro

+
    + @foreach (var point in Model.WhyGapPoints) + { +
  • @point
  • + } +
+
diff --git a/Views/_ViewImports.cshtml b/Views/_ViewImports.cshtml new file mode 100644 index 0000000..e2f0a47 --- /dev/null +++ b/Views/_ViewImports.cshtml @@ -0,0 +1,4 @@ +@using manpreetsingh.pro +@using manpreetsingh.pro.Models +@using manpreetsingh.pro.ViewModels +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/Views/_ViewStart.cshtml b/Views/_ViewStart.cshtml new file mode 100644 index 0000000..820a2f6 --- /dev/null +++ b/Views/_ViewStart.cshtml @@ -0,0 +1,3 @@ +@{ + Layout = "_Layout"; +} diff --git a/manpreetsingh.pro.csproj b/manpreetsingh.pro.csproj new file mode 100644 index 0000000..24ec0e8 --- /dev/null +++ b/manpreetsingh.pro.csproj @@ -0,0 +1,7 @@ + + + net8.0 + enable + enable + + diff --git a/wwwroot/css/site.css b/wwwroot/css/site.css new file mode 100644 index 0000000..af51eaa --- /dev/null +++ b/wwwroot/css/site.css @@ -0,0 +1,340 @@ +:root { + --bg: #f5f5f3; + --surface: #efefec; + --text: #111111; + --muted: #5b5b5b; + --border: #16161633; + --accent: #2b2b2b; + --logo-filter: none; + --space-1: clamp(0.6rem, 0.55rem + 0.2vw, 0.8rem); + --space-2: clamp(1rem, 0.8rem + 0.5vw, 1.4rem); + --space-3: clamp(1.8rem, 1.3rem + 1vw, 2.8rem); + --space-4: clamp(2.8rem, 2rem + 2vw, 5rem); + --space-5: clamp(4rem, 3rem + 3vw, 8rem); +} + +@media (prefers-color-scheme: dark) { + :root { + --bg: #0f0f0f; + --surface: #171717; + --text: #ececec; + --muted: #a9a9a9; + --border: #ffffff24; + --logo-filter: invert(1) brightness(1.1); + } +} + +html[data-theme='light'] { + --bg: #f5f5f3; + --surface: #efefec; + --text: #111111; + --muted: #5b5b5b; + --border: #16161633; + --logo-filter: none; +} + +html[data-theme='dark'] { + --bg: #0f0f0f; + --surface: #171717; + --text: #ececec; + --muted: #a9a9a9; + --border: #ffffff24; + --logo-filter: invert(1) brightness(1.1); +} + + +* { box-sizing: border-box; } +html, body { margin: 0; } +body { + background: var(--bg); + color: var(--text); + font-family: Inter, "Helvetica Neue", Arial, sans-serif; + line-height: 1.42; + transition: background-color 220ms ease, color 220ms ease; +} + +.skip-link { + position: absolute; + left: -9999px; + top: var(--space-1); + padding: 0.5rem 0.7rem; + border: 1px solid var(--text); + background: var(--bg); +} +.skip-link:focus { left: var(--space-2); } + +.site-header { + position: sticky; + top: 0; + z-index: 20; + border-bottom: 1px solid var(--border); + background: color-mix(in srgb, var(--bg) 94%, transparent); + backdrop-filter: blur(6px); +} + +.header-wrap, +.site-main, +.site-footer { + width: min(1560px, 100% - clamp(1.5rem, 1rem + 3vw, 5rem)); + margin-inline: auto; +} + +.header-wrap { + display: grid; + grid-template-columns: auto 1fr auto; + gap: var(--space-2); + align-items: center; + padding: var(--space-2) 0; +} + +.wordmark { + color: var(--text); + text-decoration: none; + font-size: 0.72rem; + letter-spacing: 0.15em; + text-transform: uppercase; + display: inline-flex; + align-items: center; + gap: 0.55rem; +} + +.brand-logo { + width: 1.3rem; + height: 1.3rem; + filter: var(--logo-filter); +} + +.main-nav { + list-style: none; + display: flex; + gap: clamp(0.65rem, 0.4rem + 0.8vw, 1.3rem); + margin: 0; + padding: 0; +} + +.main-nav a, +.inline-link, +.info-card a, +.line-item a { + color: var(--text); + text-decoration: none; + position: relative; +} + +.main-nav a, +.inline-link { + font-size: 0.7rem; + letter-spacing: 0.12em; + text-transform: uppercase; +} + +.main-nav a::after, +.inline-link::after, +.info-card a::after, +.line-item a::after { + content: ""; + position: absolute; + left: 0; + bottom: -0.2rem; + width: 100%; + height: 1px; + background: var(--accent); + transform: scaleX(0); + transform-origin: right; + transition: transform 180ms ease, background-color 220ms ease; +} + +.main-nav a:hover::after, +.main-nav a:focus-visible::after, +.inline-link:hover::after, +.inline-link:focus-visible::after, +.info-card a:hover::after, +.info-card a:focus-visible::after, +.line-item a:hover::after, +.line-item a:focus-visible::after { + transform: scaleX(1); + transform-origin: left; +} + +.header-controls { + display: flex; + align-items: center; + gap: 0.7rem; +} + +.toggle-btn { + border: 1px solid var(--border); + background: var(--surface); + color: var(--text); + font-size: 0.68rem; + letter-spacing: 0.1em; + text-transform: uppercase; + padding: 0.35rem 0.5rem; +} + + +.site-main { padding: var(--space-4) 0 var(--space-5); } +section { padding: var(--space-4) 0; border-bottom: 1px solid var(--border); } + +.hero { + display: grid; + grid-template-columns: 1fr 2fr; + gap: var(--space-3); + align-items: end; + min-height: 72vh; +} + +.identity-block { border-top: 1px solid var(--border); padding-top: var(--space-2); } +.hero-logo { + width: clamp(3rem, 2.4rem + 1.7vw, 4.3rem); + height: auto; + filter: var(--logo-filter); +} +.identity-name { + margin: 0.4rem 0 0; + color: var(--muted); + text-transform: lowercase; + letter-spacing: 0.05em; +} + +h1 { + margin: 0; + font-size: clamp(2rem, 1.3rem + 5.8vw, 7.2rem); + line-height: 0.95; + letter-spacing: 0.01em; + text-transform: uppercase; + max-width: 14ch; +} +.thesis-block p, +.page-intro p { max-width: 58ch; margin-top: var(--space-2); } + +.section-head { + display: grid; + grid-template-columns: 3rem 1fr; + gap: 1rem; + align-items: baseline; +} +.kicker { + margin: 0; + color: var(--muted); + font-size: 0.65rem; + letter-spacing: 0.14em; + text-transform: uppercase; +} + +h2, h3 { margin: 0; font-weight: 550; } +h2 { + text-transform: uppercase; + letter-spacing: 0.04em; + font-size: clamp(1.2rem, 1rem + 0.8vw, 1.8rem); +} + +.lead-copy { margin-left: 4rem; max-width: 60ch; font-size: clamp(1rem, .9rem + .5vw, 1.25rem); } +.plain-list { + margin: var(--space-2) 0 0 4rem; + padding: 0; + list-style: none; + display: grid; + gap: 0.8rem; + max-width: 72ch; +} +.plain-list li { padding-left: 1rem; border-left: 2px solid var(--accent); } + +.card-grid { + margin-top: var(--space-2); + display: grid; + grid-template-columns: repeat(12, 1fr); + gap: var(--space-2); +} +.card-grid.compact .info-card { grid-column: span 6; } +.info-card { + grid-column: span 6; + border: 1px solid var(--border); + background: var(--surface); + padding: var(--space-2); + transition: border-color 180ms ease, transform 180ms ease, background-color 220ms ease; +} +.info-card:hover, +.info-card:focus-visible { + border-color: var(--accent); + transform: translateY(-2px); +} +.info-card p { margin: 0.6rem 0 0; max-width: 48ch; } +.micro { color: var(--muted); font-size: 0.8rem; } + +.stack-list { margin-top: var(--space-2); } +.line-item { + border-top: 1px solid var(--border); + padding: 1rem 0; + transition: padding-left 180ms ease, border-color 180ms ease; +} +.line-item:hover, +.line-item:focus-visible { + padding-left: 0.7rem; + border-color: var(--accent); +} +.meta-row { + margin-top: 0.45rem; + display: flex; + justify-content: space-between; + gap: 1rem; + color: var(--muted); + text-transform: uppercase; + letter-spacing: 0.12em; + font-size: 0.64rem; +} + +.page-intro h1 { max-width: 16ch; } + +.site-footer { + padding: var(--space-3) 0; + color: var(--muted); + font-size: 0.72rem; + letter-spacing: 0.12em; + text-transform: uppercase; +} + +.site-footer p { + margin: 0; + display: inline-flex; + align-items: center; + gap: 0.45rem; +} + +.footer-logo { + width: 0.95rem; + height: 0.95rem; + filter: var(--logo-filter); +} + +.section-reveal { + opacity: 0; + transform: translateY(12px); + transition: opacity 380ms ease, transform 380ms ease; +} +.section-reveal.is-visible { opacity: 1; transform: translateY(0); } +.hero .thesis-block { opacity: 0; transform: translateY(14px); transition: opacity 480ms ease, transform 480ms ease; } +.hero.is-visible .thesis-block { opacity: 1; transform: translateY(0); } + +:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } + +@media (max-width: 1024px) { + .header-wrap { grid-template-columns: 1fr; } + .main-nav { flex-wrap: wrap; } + .hero { grid-template-columns: 1fr; min-height: auto; } + .lead-copy, + .plain-list { margin-left: 0; } + .info-card, + .card-grid.compact .info-card { grid-column: span 12; } +} + +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } + .section-reveal, + .hero .thesis-block { opacity: 1; transform: none; } +} diff --git a/wwwroot/img/Logo-Out.svg b/wwwroot/img/Logo-Out.svg new file mode 100644 index 0000000..aa927ff --- /dev/null +++ b/wwwroot/img/Logo-Out.svg @@ -0,0 +1,4 @@ + + + + diff --git a/wwwroot/img/placeholder-1.svg b/wwwroot/img/placeholder-1.svg new file mode 100644 index 0000000..bfd0403 --- /dev/null +++ b/wwwroot/img/placeholder-1.svg @@ -0,0 +1,7 @@ + + + + + + PLACEHOLDER 01 + diff --git a/wwwroot/img/placeholder-2.svg b/wwwroot/img/placeholder-2.svg new file mode 100644 index 0000000..b65ced6 --- /dev/null +++ b/wwwroot/img/placeholder-2.svg @@ -0,0 +1,6 @@ + + + + + PLACEHOLDER 02 + diff --git a/wwwroot/img/placeholder-3.svg b/wwwroot/img/placeholder-3.svg new file mode 100644 index 0000000..63459b4 --- /dev/null +++ b/wwwroot/img/placeholder-3.svg @@ -0,0 +1,6 @@ + + + + + PLACEHOLDER 03 + diff --git a/wwwroot/img/placeholder-4.svg b/wwwroot/img/placeholder-4.svg new file mode 100644 index 0000000..63a0e7f --- /dev/null +++ b/wwwroot/img/placeholder-4.svg @@ -0,0 +1,6 @@ + + + + + PLACEHOLDER 04 + diff --git a/wwwroot/js/site.js b/wwwroot/js/site.js new file mode 100644 index 0000000..2712394 --- /dev/null +++ b/wwwroot/js/site.js @@ -0,0 +1,36 @@ +(() => { + const root = document.documentElement; + const themeKey = 'ms-theme'; + const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches; + + const storedTheme = localStorage.getItem(themeKey); + if (storedTheme === 'light' || storedTheme === 'dark') { + root.setAttribute('data-theme', storedTheme); + } else { + root.setAttribute('data-theme', 'system'); + } + + const themeButton = document.querySelector('[data-theme-toggle]'); + themeButton?.addEventListener('click', () => { + const current = root.getAttribute('data-theme'); + const next = current === 'dark' ? 'light' : 'dark'; + root.setAttribute('data-theme', next); + localStorage.setItem(themeKey, next); + }); + + if (prefersReducedMotion) { + document.querySelectorAll('.section-reveal').forEach((item) => item.classList.add('is-visible')); + return; + } + + const observer = new IntersectionObserver((entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + entry.target.classList.add('is-visible'); + observer.unobserve(entry.target); + } + }); + }, { threshold: 0.15, rootMargin: '0px 0px -24px 0px' }); + + document.querySelectorAll('.section-reveal').forEach((item) => observer.observe(item)); +})();