Add new styles for home and landing pages

- Created home.css with comprehensive styles for the home page layout, including typography, buttons, and responsive design adjustments.
- Created landing.css to style the landing page, focusing on typography, layout, and responsive behavior for various screen sizes.
This commit is contained in:
syntaxbullet
2026-09-04 11:50:24 +02:00
parent 1acf016169
commit 084603bb6e
43 changed files with 7191 additions and 65 deletions

603
src/pages/DesignSystem.tsx Normal file
View File

@@ -0,0 +1,603 @@
import { useState } from "react";
import { Link } from "react-router";
import {
Button,
Checkbox,
Counter,
SectionHeading,
} from "../components/design-system/primitives";
import { CalendarHeatmap } from "../components/design-system/CalendarHeatmap";
import { HabitChart } from "../components/design-system/HabitChart";
import { EditingWorkbench } from "../components/design-system/EditingWorkbench";
import type { HabitColors } from "../components/design-system/HabitColorPicker";
import {
combinedProgress,
demoCalendar,
PROGRESS_SHADES,
HABIT_COLORS,
} from "../components/design-system/calendar-model";
import todayImage from "../assets/design/today-v2.png";
import habitImage from "../assets/design/habit-detail.png";
import "../../styles/design-system.css";
type View = "Today" | "Habit detail" | "Combined chart";
export function DesignSystem() {
const [habitColors, setHabitColors] = useState<HabitColors>({ ...HABIT_COLORS });
const [view, setView] = useState<View>("Today");
const [water, setWater] = useState(7);
const [read, setRead] = useState(true);
const [tasks, setTasks] = useState([true, true, false]);
const [movement, setMovement] = useState(10);
const [included, setIncluded] = useState([true, true, true, true]);
const [exampleChecked, setExampleChecked] = useState(false);
const [exampleCount, setExampleCount] = useState(3);
const [habitName, setHabitName] = useState("");
const [savedName, setSavedName] = useState("");
const taskCount = tasks.filter(Boolean).length;
const habits = [
{ value: water, target: 8, due: true },
{ value: Number(read), target: 1, due: true },
{ value: taskCount, target: 3, due: true },
{ value: movement, target: 20, due: true },
];
const total = combinedProgress(habits);
const combined = combinedProgress(
habits.filter((_, index) => included[index]),
);
const isIndividual = view === "Habit detail";
const chartDays = demoCalendar(
isIndividual ? water : combined.completed,
isIndividual ? 8 : combined.due,
).map((day) =>
!isIndividual && combined.due === 0 && day.state !== "future"
? { ...day, state: "not-due" as const }
: day,
);
function resetDemo() {
setWater(7);
setRead(true);
setTasks([true, true, false]);
setMovement(10);
setIncluded([true, true, true, true]);
}
return (
<div className="ds-root" id="top">
<a className="ds-skip-link" href="#ds-main">
Skip to content
</a>
<header className="ds-header">
<Link className="ds-wordmark" to="/">
minabot<span aria-hidden="true">.</span>
</Link>
<nav aria-label="Design system navigation">
<a href="#editing">Editing</a>
<a href="#foundations">Foundations</a>
<a href="#components">Components</a>
<a href="#views">
Views <span aria-hidden="true"></span>
</a>
</nav>
<span className="ds-edition">DESIGN SYSTEM / 01</span>
</header>
<main id="ds-main" className="ds-main">
<section className="ds-intro" aria-labelledby="ds-title">
<div>
<p className="ds-eyebrow">MINABOT INTERFACE LANGUAGE</p>
<h1 id="ds-title">
Less interface.
<br />
<em>More intention.</em>
</h1>
<p className="ds-intro-copy">
A quiet foundation for everyday progress.
<br />A quiet canvas. A color for every habit.
</p>
</div>
<div className="ds-intro-note">
<span className="ds-tiny-cross" aria-hidden="true">
+
</span>
<p>
Flat by design.
<br />
Space, not containers.
<br />
Clarity in every state.
</p>
<a href="#playground">
Explore the system <span aria-hidden="true"></span>
</a>
</div>
</section>
<section
className="ds-section"
id="playground"
aria-labelledby="playground-title"
>
<div className="ds-section-top">
<div>
<span className="ds-eyebrow">01 / IN PRACTICE</span>
<h2 id="playground-title">
A little, <em>every day.</em>
</h2>
</div>
<span className="ds-demo-note">
<span aria-hidden="true"></span> Interactive demo · not saved
</span>
</div>
<div className="ds-preview-toolbar">
<div
className="ds-view-switch"
role="group"
aria-label="Preview view"
>
{(["Today", "Habit detail", "Combined chart"] as const).map(
(item) => (
<button
type="button"
key={item}
aria-pressed={view === item}
onClick={() => setView(item)}
>
{item}
</button>
),
)}
</div>
<Button variant="text" onClick={resetDemo}>
Reset demo <span aria-hidden="true"></span>
</Button>
</div>
<div className="ds-preview-heading">
<div>
<p className="ds-eyebrow">FRIDAY, SEPTEMBER 4, 2026</p>
<h3>
{view === "Today"
? "Your habits, at a glance."
: isIndividual
? "Drink water"
: "Your rhythm, together."}
</h3>
</div>
<p aria-live="polite">
{view === "Today"
? `${total.completed} of ${total.due} complete`
: isIndividual
? "Daily · 8 glasses"
: combined.due
? `${combined.completed} of ${combined.due} habits complete`
: "No habits selected"}
</p>
</div>
{view === "Today" ? (
<div className="ds-habit-chart-grid">
<HabitChart
name="Drink water"
method="Count target"
value={water}
target={8}
unit="glasses"
color={habitColors.water}
>
<Counter
label="glasses of water"
value={water}
target={8}
onChange={setWater}
/>
</HabitChart>
<HabitChart
name="Read a little"
method="Manual checkbox"
value={Number(read)}
target={1}
unit="reading session"
color={habitColors.reading}
>
<Checkbox
label="Mark reading done"
className="ds-checkbox--icon"
checked={read}
onChange={(event) => setRead(event.target.checked)}
/>
</HabitChart>
<HabitChart
name="Evening reset"
method="Task-based"
value={taskCount}
target={3}
unit="tasks"
color={habitColors.tasks}
tasks={["Clear desk", "Plan tomorrow", "Stretch"].map(
(label, index) => (
<Checkbox
key={label}
label={label}
checked={tasks[index]}
onChange={(event) =>
setTasks((current) =>
current.map((done, i) =>
i === index ? event.target.checked : done,
),
)
}
/>
),
)}
/>
<HabitChart
name="Move a little"
method="Count target"
value={movement}
target={20}
unit="minutes"
color={habitColors.movement}
>
<Counter
label="minutes of movement"
value={movement}
target={20}
onChange={setMovement}
/>
</HabitChart>
<p className="ds-footnote ds-habit-chart-grid-note">
Each habit has its own rhythm. Expand tasks to log your day;
only fully completed habits count in a combined chart.
</p>
</div>
) : (
<div className="ds-chart-preview">
{isIndividual ? (
<div className="ds-detail-count">
<span className="ds-big-value">
{water}
<span className="ds-muted"> / 8</span>
<small>glasses</small>
</span>
<Counter
label="detail glasses"
value={water}
target={8}
onChange={setWater}
/>
</div>
) : (
<div
className="ds-chart-selection"
role="group"
aria-label="Included habits"
>
{[
"Drink water",
"Read a little",
"Evening reset",
"Move a little",
].map((label, index) => (
<Checkbox
key={label}
label={label}
checked={included[index]}
onChange={(event) =>
setIncluded((current) =>
current.map((checked, i) =>
i === index ? event.target.checked : checked,
),
)
}
/>
))}
</div>
)}
<CalendarHeatmap
key={view}
days={chartDays}
unit={isIndividual ? "glasses" : "habits complete"}
label={
isIndividual
? "Drink water progress calendar"
: "Combined habit calendar"
}
color={isIndividual ? habitColors.water : "#111111"}
/>
<p className="ds-footnote">
{isIndividual
? "Partial progress is visible here. Only 8 of 8 glasses counts as complete in a combined chart."
: "Equal weight. Only fully completed, due habits count. Nothing scheduled means no score, not a missed day."}
</p>
</div>
)}
</section>
<EditingWorkbench onColorsChange={setHabitColors} />
<section className="ds-section ds-split-section" id="foundations">
<SectionHeading number="03 / FOUNDATIONS" title="The essentials.">
A small vocabulary.
<br />
Used with intention.
</SectionHeading>
<div className="ds-section-content">
<div className="ds-spec-heading">
<h3>Typography</h3>
<span className="ds-code">Instrument Serif + system sans</span>
</div>
<div className="ds-type-specimen">
<span className="ds-type-display">
Small steps.
<br />
<em>Lasting rhythm.</em>
</span>
<p>Instrument Serif · Regular & italic · Display</p>
</div>
<div className="ds-body-specimen">
<span>Room to breathe. A clear next step.</span>
<p>System sans · 1416px · Body & controls</p>
</div>
<div className="ds-spec-heading ds-spec-heading--spaced">
<h3>Monochrome</h3>
<span className="ds-code">The interface stays neutral</span>
</div>
<div className="ds-palette">
{[
{ name: "Ink", value: "#111111" },
{ name: "Secondary", value: "#666666" },
{ name: "Rule", value: "#DEDEDE" },
{ name: "Paper", value: "#FFFFFF" },
].map((color) => (
<div key={color.name}>
<div
className="ds-swatch"
style={{ background: color.value }}
/>
<p>
{color.name}
<span className="ds-code">{color.value}</span>
</p>
</div>
))}
</div>
<div className="ds-spec-heading ds-spec-heading--spaced">
<h3>Habit colors</h3>
<span className="ds-code">Identity, not decoration</span>
</div>
<div className="ds-habit-color-key">
{Object.entries(HABIT_COLORS).map(([name, color]) => (
<span key={name}>
<i style={{ backgroundColor: color }} aria-hidden="true" />
{name}
</span>
))}
</div>
<div className="ds-spec-heading ds-spec-heading--spaced">
<h3>Space & structure</h3>
<span className="ds-code">4px base</span>
</div>
<div className="ds-spacing">
{[4, 8, 16, 24, 32, 48].map((space) => (
<div key={space}>
<span style={{ width: space }} />
<code>{space}</code>
</div>
))}
</div>
<p className="ds-footnote">
Square corners. One-pixel rules. No cards, shadows, or decorative
surfaces.
</p>
</div>
</section>
<section className="ds-section ds-split-section" id="components">
<SectionHeading number="04 / COMPONENTS" title="Only what matters.">
Purpose-built primitives.
<br />
Try them for yourself.
</SectionHeading>
<div className="ds-section-content">
<div className="ds-spec-heading">
<h3>Actions</h3>
<span className="ds-code">Button</span>
</div>
<div className="ds-actions">
<Button
onClick={() => {
document.getElementById("habit-name")?.focus();
}}
>
Add a habit <span aria-hidden="true">+</span>
</Button>
<Button
variant="secondary"
onClick={() => {
setExampleCount(3);
setExampleChecked(false);
setHabitName("");
setSavedName("");
}}
>
Reset examples
</Button>
<Button
variant="text"
onClick={() => {
setView("Habit detail");
document.getElementById("playground")?.scrollIntoView();
}}
>
View habit <span aria-hidden="true"></span>
</Button>
<Button disabled>Unavailable</Button>
</div>
<div className="ds-component-row">
<div>
<h3>A simple yes</h3>
<span className="ds-code">Checkbox</span>
</div>
<Checkbox
label={exampleChecked ? "Done for today" : "Mark as done"}
checked={exampleChecked}
onChange={(event) => setExampleChecked(event.target.checked)}
/>
</div>
<div className="ds-component-row">
<div>
<h3>A little more</h3>
<span className="ds-code">Counter · bounded 08</span>
</div>
<Counter
label="example count"
value={exampleCount}
target={8}
onChange={setExampleCount}
/>
</div>
<form
className="ds-example-form"
onSubmit={(event) => {
event.preventDefault();
if (habitName.trim()) setSavedName(habitName.trim());
}}
>
<div className="ds-spec-heading">
<label htmlFor="habit-name">Give it a name</label>
<span className="ds-code">Text input</span>
</div>
<div className="ds-input-row">
<input
id="habit-name"
required
maxLength={80}
placeholder="e.g. Read ten pages"
value={habitName}
onChange={(event) => {
setHabitName(event.target.value);
setSavedName("");
}}
aria-describedby="name-hint"
/>
<Button
type="submit"
variant="secondary"
disabled={!habitName.trim()}
>
Try it <span aria-hidden="true"></span>
</Button>
</div>
<p id="name-hint" className="ds-footnote" role="status">
{savedName
? `${savedName}” — preview accepted. No habit was created.`
: "Keep it short and personal. This is a form preview, not a new habit."}
</p>
</form>
</div>
</section>
<section className="ds-section ds-split-section" id="calendar-states">
<SectionHeading
number="05 / PROGRESS LANGUAGE"
title="Every shade counts."
>
Progress is a spectrum.
<br />
Completion is a clear rule.
</SectionHeading>
<div className="ds-section-content">
<div className="ds-spec-heading">
<h3>One glass at a time</h3>
<span className="ds-code">CalendarHeatmap</span>
</div>
<div className="ds-shade-scale">
{PROGRESS_SHADES.map((color, index) => (
<div key={color}>
<span style={{ backgroundColor: color }} />
<small>{index}/8</small>
</div>
))}
</div>
<div className="ds-scale-caption">
<span>Due · no progress</span>
<span>Partial progress</span>
<span>Complete</span>
</div>
<div className="ds-neutral-states">
<div>
<span className="ds-state-square ds-state-square--not-due" />
<div>
<h4>Nothing scheduled</h4>
<p>A neutral dot, not a missed day. Excluded from the score.</p>
</div>
</div>
<div>
<span className="ds-state-square ds-state-square--future" />
<div>
<h4>Upcoming</h4>
<p>Same fill as zero. Labeled Upcoming on inspection.</p>
</div>
</div>
</div>
<p className="ds-footnote">
Calendar cells expose exact values and status on selection, hover,
and keyboard focus. Shade is never the only signal.
</p>
</div>
</section>
<section className="ds-section" id="views">
<div className="ds-section-top">
<div>
<span className="ds-eyebrow">06 / VISUAL DIRECTION</span>
<h2>From idea to interface.</h2>
</div>
<p className="ds-muted">
Two imagegen studies, grounded in the PRD.
</p>
</div>
<div className="ds-reference-grid">
<figure>
<a href={todayImage} target="_blank" rel="noreferrer">
<img
src={todayImage}
alt="Generated Today view: four colored habit charts in a two-column grid with an expanded task accordion, serif headings, and no cards"
loading="lazy"
/>
</a>
<figcaption>
<span>01 Today</span>
<span className="ds-muted">
Daily progress <span aria-hidden="true"></span>
</span>
</figcaption>
</figure>
<figure>
<a href={habitImage} target="_blank" rel="noreferrer">
<img
src={habitImage}
alt="Generated Drink water detail view: large seven-of-eight count and monochromatic calendar"
loading="lazy"
/>
</a>
<figcaption>
<span>02 Habit detail</span>
<span className="ds-muted">
A closer look <span aria-hidden="true"></span>
</span>
</figcaption>
</figure>
</div>
<p className="ds-footnote">
Visual references, not authoritative calendar data. The interactive
components above implement exact counts and distinct date states.
</p>
</section>
<footer className="ds-footer">
<span className="ds-wordmark">minabot.</span>
<span>A little, every day.</span>
<a href="#top">Back to top </a>
</footer>
</main>
</div>
);
}