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:
603
src/pages/DesignSystem.tsx
Normal file
603
src/pages/DesignSystem.tsx
Normal 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 · 14–16px · 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 0–8</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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user