Skip to content

Commit e6010d5

Browse files
authored
Fix mobile layout issues (#7)
**Summary** Fixes mobile layout issues across the homepage and blog, improving spacing, readability, and navigation on small screens. **Changes** - Add a mobile header menu toggle with responsive sizing. - Make work and lab layouts mobile-first single-column with adjusted text sizes. - Tighten section spacing in hero, philosophy, work, lab, and footer. - Refresh README projects plus configuration, RSS, and testing notes.
2 parents 0dafe43 + 5adfdae commit e6010d5

8 files changed

Lines changed: 155 additions & 59 deletions

File tree

README.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@ We build software that feels more human without becoming less powerful.
66

77
## Projects
88

9+
- **[Threshold](https://github.com/liminal-hq/threshold)** – Flexible time window alarms for life-first scheduling.
910
- **[Liminal Notes](https://github.com/ScottMorris/liminal-notes)** – Local-first, Markdown-based note-taking.
1011
- **[City Sim 1000](https://github.com/ScottMorris/city-sim-1000)** – Low-poly city simulation in the browser.
1112
- **[SMDU](https://github.com/ScottMorris/smdu)** – Terminal disk usage analyser.
13+
- **[Coherence Chat Exporter](https://github.com/liminal-hq/coherence-chat-exporter)** – CLI for archiving AI conversations into organised, tagged Markdown across providers.
14+
- **[Keep Note Converter](https://github.com/ScottMorris/keep-note-converter)** – Installable Next.js PWA that converts pasted rich text into Google Keep-compatible markup.
1215

1316
## Site Architecture
1417

@@ -18,6 +21,8 @@ This portfolio is built with modern web standards:
1821
- **React 19**
1922
- **Tailwind CSS 4**
2023
- **TypeScript**
24+
- **Metadata routes** for `sitemap.xml` and `robots.txt`
25+
- **RSS feed** generated during build
2126

2227
## Development
2328

@@ -28,6 +33,13 @@ pnpm dev
2833

2934
Open [http://localhost:3000](http://localhost:3000) to view locally.
3035

36+
## Configuration
37+
38+
Environment variables used by the site:
39+
40+
- `NEXT_PUBLIC_SITE_URL` or `SITE_URL` for metadata, sitemap, robots, and RSS URLs (defaults to `https://liminalhq.ca`).
41+
- `PAGES_BASE_PATH` for GitHub Pages deployments when the site is served from a subpath.
42+
3143
## Deployment
3244

3345
Automated via **GitHub Actions** to GitHub Pages. Pushing to `main` builds and deploys the `out/` directory.
@@ -56,4 +68,10 @@ Draft behaviour:
5668
RSS feed:
5769
- Generated at build time to `public/blog/feed.xml`.
5870
- Published URL: `/blog/feed.xml`.
59-
- Override site URL by setting `SITE_URL` during build (default: `https://liminalhq.ca`).
71+
- Override site URL by setting `SITE_URL` or `NEXT_PUBLIC_SITE_URL` during build (default: `https://liminalhq.ca`).
72+
73+
## Testing
74+
75+
```bash
76+
pnpm test
77+
```

src/app/globals.css

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,16 @@ h3 {
8686
padding: 6rem 0;
8787
}
8888

89+
@media (max-width: 768px) {
90+
.container-custom {
91+
padding: 0 1.25rem;
92+
}
93+
94+
.section {
95+
padding: 4rem 0;
96+
}
97+
}
98+
8999
.blog-prose {
90100
color: var(--text-color);
91101
font-size: 1.05rem;

src/components/Footer.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import Image from "next/image";
22

33
export default function Footer() {
44
return (
5-
<footer className="py-20 mt-20 border-t border-[rgba(255,255,255,0.14)] text-[var(--text-muted)] text-sm flex justify-between items-center">
5+
<footer className="mt-14 flex flex-col gap-6 border-t border-[rgba(255,255,255,0.14)] py-10 text-sm text-[var(--text-muted)] md:flex-row md:items-center md:justify-between md:py-16">
66
<div>
77
<span className="mb-2 flex items-center gap-2 font-bold tracking-[0.05em] text-white [font-feature-settings:smcp] [font-variant:small-caps]">
88
<Image src="/liminalhq-mark-v1.svg" alt="" width={20} height={20} className="h-5 w-5" />
@@ -11,7 +11,7 @@ export default function Footer() {
1111
<p>Designed & Coded in Canada 🍁</p>
1212
</div>
1313

14-
<div className="text-right">
14+
<div className="text-left md:text-right">
1515
<p>&copy; 2026 Liminal HQ</p>
1616
<p className="mt-2">
1717
<a href="https://github.com/liminal-hq" className="hover:text-white transition-colors">

src/components/Header.tsx

Lines changed: 90 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
"use client";
2+
3+
import { useState } from "react";
14
import Link from "next/link";
25
import Image from "next/image";
36

@@ -7,40 +10,97 @@ const navItems = [
710
{ label: "Philosophy", href: "/#philosophy" },
811
{ label: "Lab", href: "/#lab" },
912
{ label: "Blog", href: "/blog" },
13+
{ label: "Contact", href: "mailto:contact@liminalhq.ca", external: true },
1014
];
1115

1216
export default function Header() {
17+
const [isMenuOpen, setIsMenuOpen] = useState(false);
18+
19+
const toggleMenu = () => {
20+
setIsMenuOpen((current) => !current);
21+
};
22+
23+
const closeMenu = () => {
24+
setIsMenuOpen(false);
25+
};
26+
1327
return (
14-
<header className="flex items-center justify-between py-8">
15-
<Link
16-
href="/"
17-
className="flex items-center gap-3 text-xl font-bold tracking-[0.08em] text-white [font-feature-settings:smcp] [font-variant:small-caps]"
18-
>
19-
<Image src="/liminalhq-mark-v1.svg" alt="" width={28} height={28} className="h-7 w-7" />
20-
liminal hq
21-
</Link>
22-
<nav>
23-
<ul className="flex list-none gap-10">
24-
{navItems.map((item) => (
25-
<li key={item.label}>
26-
<Link
27-
href={item.href}
28-
className="relative text-[0.95rem] font-medium text-[#9ca3af] transition-all duration-200 hover:text-white hover:shadow-[0_0_8px_rgba(255,255,255,0.5)]"
29-
>
30-
{item.label}
31-
</Link>
32-
</li>
33-
))}
34-
<li>
35-
<Link
36-
href="mailto:contact@liminalhq.ca"
37-
className="relative text-[0.95rem] font-medium text-[#9ca3af] transition-all duration-200 hover:text-white hover:shadow-[0_0_8px_rgba(255,255,255,0.5)]"
38-
>
39-
Contact
40-
</Link>
41-
</li>
42-
</ul>
43-
</nav>
28+
<header className="py-6 md:py-8">
29+
<div className="flex items-center justify-between">
30+
<Link
31+
href="/"
32+
className="flex items-center gap-3 text-lg font-bold tracking-[0.08em] text-white [font-feature-settings:smcp] [font-variant:small-caps] md:text-xl"
33+
onClick={closeMenu}
34+
>
35+
<Image src="/liminalhq-mark-v1.svg" alt="" width={28} height={28} className="h-7 w-7" />
36+
liminal hq
37+
</Link>
38+
39+
<button
40+
type="button"
41+
className="inline-flex items-center gap-2 rounded-full border border-[rgba(255,255,255,0.2)] px-4 py-2 text-xs font-semibold uppercase tracking-[0.2em] text-white transition hover:border-[rgba(255,255,255,0.4)] md:hidden"
42+
aria-expanded={isMenuOpen}
43+
aria-controls="site-nav"
44+
onClick={toggleMenu}
45+
>
46+
{isMenuOpen ? "Close" : "Menu"}
47+
</button>
48+
49+
<nav className="hidden md:block">
50+
<ul className="flex list-none gap-8">
51+
{navItems.map((item) => (
52+
<li key={item.label}>
53+
{item.external ? (
54+
<a
55+
href={item.href}
56+
className="relative text-[0.95rem] font-medium text-[#9ca3af] transition-all duration-200 hover:text-white hover:shadow-[0_0_8px_rgba(255,255,255,0.5)]"
57+
>
58+
{item.label}
59+
</a>
60+
) : (
61+
<Link
62+
href={item.href}
63+
className="relative text-[0.95rem] font-medium text-[#9ca3af] transition-all duration-200 hover:text-white hover:shadow-[0_0_8px_rgba(255,255,255,0.5)]"
64+
>
65+
{item.label}
66+
</Link>
67+
)}
68+
</li>
69+
))}
70+
</ul>
71+
</nav>
72+
</div>
73+
74+
{isMenuOpen ? (
75+
<nav
76+
id="site-nav"
77+
className="mt-5 rounded-2xl border border-[rgba(255,255,255,0.12)] bg-[rgba(12,12,18,0.9)] p-5 shadow-[0_20px_50px_rgba(0,0,0,0.35)] backdrop-blur md:hidden"
78+
>
79+
<ul className="flex list-none flex-col gap-4">
80+
{navItems.map((item) => (
81+
<li key={item.label}>
82+
{item.external ? (
83+
<a
84+
href={item.href}
85+
className="text-base font-medium text-[#e2e8f0] transition-colors hover:text-white"
86+
onClick={closeMenu}
87+
>
88+
{item.label}
89+
</a>
90+
) : (
91+
<Link
92+
href={item.href}
93+
className="text-base font-medium text-[#e2e8f0] transition-colors hover:text-white"
94+
onClick={closeMenu}
95+
>
96+
{item.label}
97+
</Link>
98+
)}
99+
</li>
100+
))}
101+
</ul>
102+
</nav>
103+
) : null}
44104
</header>
45105
);
46106
}

src/components/HeroSection.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,20 @@ import styles from "./HeroSection.module.css";
22

33
export default function HeroSection() {
44
return (
5-
<section className="py-16 pb-24">
5+
<section className="py-10 pb-16 md:py-14 md:pb-20">
66
<div className={styles.heroBanner}>
7-
<div className="relative z-10 px-16 py-12 flex flex-col justify-center">
7+
<div className="relative z-10 flex flex-col justify-center px-8 py-10 md:px-16 md:py-12">
88
<span className={styles.taglineBadge}>
99
EST. 2025 • Kitchener, ON
1010
</span>
1111
<h1 className={styles.heroTitle}>
12-
Digital tools for the<br />
12+
Digital tools for the{" "}
13+
<span className="hidden md:inline">
14+
<br />
15+
</span>
1316
spaces in between.
1417
</h1>
15-
<p className="text-lg max-w-[90%] text-[var(--text-muted)] mb-8 font-light">
18+
<p className="mb-8 max-w-[90%] text-base font-light text-[var(--text-muted)] md:text-lg">
1619
We are <strong>Liminal HQ</strong>, an independent software studio building local-first applications.
1720
We prioritize user agency, privacy, and calm computing.
1821
</p>

src/components/LabSection.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,26 @@ export default async function LabSection() {
1414
const latestPosts = posts.slice(0, 3);
1515

1616
return (
17-
<section id="lab" className="py-24">
18-
<h2 className="mb-3 text-[2rem] font-semibold text-white">From the Blog</h2>
17+
<section id="lab" className="py-16 md:py-20">
18+
<h2 className="mb-3 text-[1.75rem] font-semibold text-white md:text-[2rem]">From the Blog</h2>
1919
<p className="mb-8 max-w-3xl text-[var(--text-muted)]">
2020
Recent writing on local-first software, tooling, and design decisions from current studio work.
2121
</p>
2222
<ul className="list-none">
2323
{latestPosts.map((post, index) => (
2424
<li
2525
key={post.slug}
26-
className={`grid grid-cols-[120px_1fr] items-center gap-4 border-b border-[rgba(255,255,255,0.1)] py-8 transition-all duration-300 hover:border-l-[3px] hover:border-l-[#22d3ee] hover:bg-[linear-gradient(90deg,rgba(255,255,255,0.03),transparent)] hover:pl-4 ${
26+
className={`grid grid-cols-1 items-start gap-2 border-b border-[rgba(255,255,255,0.1)] py-6 transition-all duration-300 md:grid-cols-[120px_1fr] md:items-center md:gap-4 md:py-8 md:hover:border-l-[3px] md:hover:border-l-[#22d3ee] md:hover:bg-[linear-gradient(90deg,rgba(255,255,255,0.03),transparent)] md:hover:pl-4 ${
2727
index === 0 ? "border-t" : ""
2828
}`}
2929
>
30-
<span className="font-[var(--font-space-grotesk)] text-[0.9rem] font-semibold text-[#22d3ee]">
30+
<span className="font-[var(--font-space-grotesk)] text-[0.8rem] font-semibold text-[#22d3ee] md:text-[0.9rem]">
3131
{formatLabDate(post.date)}
3232
</span>
33-
<Link href={`/blog/${post.slug}`} className="text-[1.25rem] font-medium text-[#e0e0e0] hover:text-white">
33+
<Link
34+
href={`/blog/${post.slug}`}
35+
className="text-[1.1rem] font-medium text-[#e0e0e0] hover:text-white md:text-[1.25rem]"
36+
>
3437
{post.title}
3538
</Link>
3639
</li>

src/components/PhilosophySection.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
export default function PhilosophySection() {
22
return (
3-
<section id="philosophy" className="py-24">
3+
<section id="philosophy" className="py-16 md:py-20">
44
<div className="mb-8">
5-
<h2 className="mb-3 text-[2rem] font-semibold text-white">Our Approach</h2>
5+
<h2 className="mb-3 text-[1.75rem] font-semibold text-white md:text-[2rem]">Our Approach</h2>
66
<p className="max-w-3xl text-[var(--text-muted)]">
77
We build software that feels more human without becoming less powerful. That means every product decision is
88
evaluated against clarity, agency, and long-term ownership.
99
</p>
1010
</div>
11-
<div className="grid grid-cols-[repeat(auto-fit,minmax(280px,1fr))] gap-8 mt-4">
12-
<div className="bg-[rgba(20,20,25,0.4)] border border-[rgba(255,255,255,0.1)] p-10 rounded-2xl transition-all duration-300 backdrop-blur-[10px] hover:border-[#a78bfa] hover:-translate-y-[5px] hover:bg-[rgba(255,255,255,0.05)] hover:shadow-[0_10px_30px_rgba(0,0,0,0.3)]">
11+
<div className="mt-4 grid grid-cols-1 gap-6 md:grid-cols-[repeat(auto-fit,minmax(260px,1fr))] md:gap-8">
12+
<div className="rounded-2xl border border-[rgba(255,255,255,0.1)] bg-[rgba(20,20,25,0.4)] p-8 backdrop-blur-[10px] transition-all duration-300 hover:-translate-y-[5px] hover:border-[#a78bfa] hover:bg-[rgba(255,255,255,0.05)] hover:shadow-[0_10px_30px_rgba(0,0,0,0.3)] md:p-10">
1313
<h3 className="text-xl mb-4 text-white flex items-center gap-3">
1414
<div className="w-10 h-10 flex items-center justify-center rounded-[10px] text-[1.2rem] text-[#ffaa40] bg-[rgba(255,170,64,0.1)]">
1515
💾
@@ -21,7 +21,7 @@ export default function PhilosophySection() {
2121
default.
2222
</p>
2323
</div>
24-
<div className="bg-[rgba(20,20,25,0.4)] border border-[rgba(255,255,255,0.1)] p-10 rounded-2xl transition-all duration-300 backdrop-blur-[10px] hover:border-[#a78bfa] hover:-translate-y-[5px] hover:bg-[rgba(255,255,255,0.05)] hover:shadow-[0_10px_30px_rgba(0,0,0,0.3)]">
24+
<div className="rounded-2xl border border-[rgba(255,255,255,0.1)] bg-[rgba(20,20,25,0.4)] p-8 backdrop-blur-[10px] transition-all duration-300 hover:-translate-y-[5px] hover:border-[#a78bfa] hover:bg-[rgba(255,255,255,0.05)] hover:shadow-[0_10px_30px_rgba(0,0,0,0.3)] md:p-10">
2525
<h3 className="text-xl mb-4 text-white flex items-center gap-3">
2626
<div className="w-10 h-10 flex items-center justify-center rounded-[10px] text-[1.2rem] text-[#f43f5e] bg-[rgba(244,63,94,0.1)]">
2727
🛡️
@@ -32,7 +32,7 @@ export default function PhilosophySection() {
3232
You should be able to leave any tool without losing your work. We prefer open formats and portable data.
3333
</p>
3434
</div>
35-
<div className="bg-[rgba(20,20,25,0.4)] border border-[rgba(255,255,255,0.1)] p-10 rounded-2xl transition-all duration-300 backdrop-blur-[10px] hover:border-[#a78bfa] hover:-translate-y-[5px] hover:bg-[rgba(255,255,255,0.05)] hover:shadow-[0_10px_30px_rgba(0,0,0,0.3)]">
35+
<div className="rounded-2xl border border-[rgba(255,255,255,0.1)] bg-[rgba(20,20,25,0.4)] p-8 backdrop-blur-[10px] transition-all duration-300 hover:-translate-y-[5px] hover:border-[#a78bfa] hover:bg-[rgba(255,255,255,0.05)] hover:shadow-[0_10px_30px_rgba(0,0,0,0.3)] md:p-10">
3636
<h3 className="text-xl mb-4 text-white flex items-center gap-3">
3737
<div className="w-10 h-10 flex items-center justify-center rounded-[10px] text-[1.2rem] text-[#22d3ee] bg-[rgba(34,211,238,0.1)]">
3838
🧶

src/components/WorkSection.tsx

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ export default function WorkSection() {
1414
const flagshipPrimaryUrl = getPrimaryProjectUrl(flagship);
1515

1616
return (
17-
<section id="work" className="py-24">
18-
<h2 className="mb-8 text-[2rem] font-semibold text-white">Selected Work</h2>
17+
<section id="work" className="py-16 md:py-20">
18+
<h2 className="mb-8 text-[1.75rem] font-semibold text-white md:text-[2rem]">Selected Work</h2>
1919

2020
<article className={`${styles.projectCard} group relative cursor-pointer`}>
2121
<a
@@ -33,8 +33,10 @@ export default function WorkSection() {
3333
>
3434
{flagship.label}
3535
</span>
36-
<h3 className="mb-4 text-[2.5rem] text-white">{flagship.title}</h3>
37-
<p className="mb-10 max-w-[650px] text-[1.15rem] text-[var(--text-muted)]">{flagship.description}</p>
36+
<h3 className="mb-4 text-[2rem] text-white md:text-[2.5rem]">{flagship.title}</h3>
37+
<p className="mb-10 max-w-[650px] text-base text-[var(--text-muted)] md:text-[1.15rem]">
38+
{flagship.description}
39+
</p>
3840

3941
<div className="flex flex-wrap items-center gap-5">
4042
<span className="inline-flex items-center gap-[10px] rounded-[30px] bg-[rgba(255,255,255,0.1)] px-6 py-3 font-semibold text-white transition-all duration-300 group-hover:translate-x-[5px] group-hover:bg-[var(--accent-orange)] group-hover:text-black">
@@ -54,7 +56,7 @@ export default function WorkSection() {
5456
</div>
5557
</article>
5658

57-
<div className="mb-12 grid grid-cols-[repeat(auto-fit,minmax(300px,1fr))] gap-8">
59+
<div className="mb-12 grid grid-cols-1 gap-6 md:grid-cols-[repeat(auto-fit,minmax(280px,1fr))] md:gap-8">
5860
{supportingSelectedWork.map((project) => {
5961
const accent = getProjectAccentStyles(project.accentColour);
6062
const primaryUrl = getPrimaryProjectUrl(project);
@@ -80,8 +82,8 @@ export default function WorkSection() {
8082
>
8183
{project.label}
8284
</span>
83-
<h3 className="mb-4 mt-2 text-[2rem] text-white">{project.title}</h3>
84-
<p className="mb-6 text-[var(--text-muted)]">{project.description}</p>
85+
<h3 className="mb-4 mt-2 text-[1.6rem] text-white md:text-[2rem]">{project.title}</h3>
86+
<p className="mb-6 text-sm text-[var(--text-muted)] md:text-base">{project.description}</p>
8587

8688
<div className="flex items-center gap-4">
8789
<span className="border-b pb-[2px] text-[0.9rem] text-white" style={{ borderColor: project.accentColour }}>
@@ -104,8 +106,8 @@ export default function WorkSection() {
104106
})}
105107
</div>
106108

107-
<h3 className="mb-6 text-[1.35rem] font-semibold text-white">Experiments &amp; Tools</h3>
108-
<div className="grid grid-cols-[repeat(auto-fit,minmax(300px,1fr))] gap-8">
109+
<h3 className="mb-6 text-[1.15rem] font-semibold text-white md:text-[1.35rem]">Experiments &amp; Tools</h3>
110+
<div className="grid grid-cols-1 gap-6 md:grid-cols-[repeat(auto-fit,minmax(280px,1fr))] md:gap-8">
109111
{experiments.map((project) => {
110112
const accent = getProjectAccentStyles(project.accentColour);
111113
const primaryUrl = getPrimaryProjectUrl(project);
@@ -131,8 +133,8 @@ export default function WorkSection() {
131133
>
132134
{project.label}
133135
</span>
134-
<h4 className="mb-4 mt-2 text-[2rem] text-white">{project.title}</h4>
135-
<p className="mb-6 text-[var(--text-muted)]">{project.description}</p>
136+
<h4 className="mb-4 mt-2 text-[1.6rem] text-white md:text-[2rem]">{project.title}</h4>
137+
<p className="mb-6 text-sm text-[var(--text-muted)] md:text-base">{project.description}</p>
136138
<div className="flex items-center gap-4">
137139
<span className="border-b pb-[2px] text-[0.9rem] text-white" style={{ borderColor: project.accentColour }}>
138140
{project.siteUrl ? "Visit Site" : "View Source"}

0 commit comments

Comments
 (0)