feat: removed timeline and dark theme
Build and Deploy / build-and-push (push) Successful in 55s Details

This commit is contained in:
Ludo 2026-05-04 15:35:12 +02:00
parent edb0a52e41
commit fc9bc3fe62
8 changed files with 363 additions and 509 deletions

View File

@ -1,51 +1,60 @@
main { main {
max-width: 1400px; max-width: 960px;
margin: 0 auto; margin: 0 auto;
padding: 40px 20px; padding: 48px 24px;
min-height: 100vh; min-height: 100vh;
} }
@media (max-width: 768px) { @media (max-width: 768px) {
main { main {
padding: 20px 12px; padding: 24px 16px;
} }
} }
h1 { h1 {
font-size: 42px; font-size: 28px;
font-weight: 700; font-weight: 700;
margin-bottom: 16px; color: #111;
color: #2c3e50; margin: 0 0 8px 0;
letter-spacing: -0.5px;
} }
@media (max-width: 768px) { @media (max-width: 768px) {
h1 { h1 {
font-size: 32px; font-size: 24px;
} }
} }
.subtitle { /* Shared section title — used by TodaysTasks and Timeline */
color: var(--text); .section-title {
margin: 0; font-size: 18px;
font-weight: 600;
color: #111;
margin: 0 0 16px 0;
padding-bottom: 10px;
border-bottom: 1px solid #e8e8e8;
}
.section-title--clickable {
cursor: pointer;
display: flex;
align-items: center;
gap: 10px;
user-select: none;
}
.section-title--clickable:hover {
color: #333;
} }
.status { .status {
display: inline-block; display: inline-block;
margin-bottom: 40px; margin-bottom: 40px;
padding: 8px 16px; padding: 6px 12px;
border-radius: 6px; border-radius: 4px;
font-size: 13px; font-size: 12px;
font-weight: 500; color: #888;
}
.status--loading {
background: #f5f5f5; background: #f5f5f5;
color: #666;
}
.status--ok {
background: #f5f5f5;
color: #666;
} }
.status--error { .status--error {
@ -57,47 +66,43 @@ h1 {
position: fixed; position: fixed;
top: 20px; top: 20px;
right: 20px; right: 20px;
background: #ffebee; background: white;
color: #c62828; color: #111;
border: 1px solid #e8e8e8;
padding: 12px 16px; padding: 12px 16px;
border-radius: 6px; border-radius: 4px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
z-index: 1000; z-index: 1000;
display: flex; display: flex;
align-items: center; align-items: center;
gap: 12px; gap: 12px;
max-width: 400px; max-width: 380px;
animation: slideIn 0.3s ease; font-size: 14px;
animation: slideIn 0.2s ease;
} }
@media (max-width: 768px) { @media (max-width: 768px) {
.error-message { .error-message {
left: 12px; left: 16px;
right: 12px; right: 16px;
max-width: none; max-width: none;
} }
} }
@keyframes slideIn { @keyframes slideIn {
from { from { transform: translateY(-8px); opacity: 0; }
transform: translateX(100%); to { transform: translateY(0); opacity: 1; }
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
} }
.error-dismiss { .error-dismiss {
background: none; background: none;
border: none; border: none;
color: #c62828; color: #999;
cursor: pointer; cursor: pointer;
font-size: 18px; font-size: 16px;
padding: 0; padding: 0;
width: 24px; width: 20px;
height: 24px; height: 20px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@ -105,7 +110,7 @@ h1 {
} }
.error-dismiss:hover { .error-dismiss:hover {
opacity: 0.7; color: #111;
} }

View File

@ -122,13 +122,13 @@ function App() {
<p>Loading tasks...</p> <p>Loading tasks...</p>
) : ( ) : (
<> <>
<Timeline tasks={tasks} />
<TodaysTasks <TodaysTasks
tasks={todaysTasks} tasks={todaysTasks}
onComplete={handleCompleteTask} onComplete={handleCompleteTask}
onRenew={handleRenewTask} onRenew={handleRenewTask}
/> />
<CreateTaskForm onCreateTask={handleCreateTask} /> <CreateTaskForm onCreateTask={handleCreateTask} />
<Timeline tasks={tasks} />
</> </>
)} )}
</main> </main>

View File

@ -1,6 +1,6 @@
.create-task-form { .create-task-form {
width: 100%; width: 100%;
max-width: 600px; max-width: 100%;
margin: 0 auto; margin: 0 auto;
} }

View File

@ -1,141 +1,120 @@
.timeline-container { .upcoming-section {
width: 100%; margin-top: 48px;
padding: 40px 20px; padding-top: 32px;
background: #fafafa; border-top: 1px solid #e8e8e8;
border-radius: 12px; }.upcoming-list {
margin-bottom: 30px;
overflow-x: auto;
}
@media (max-width: 768px) {
.timeline-container {
padding: 24px 12px;
margin-bottom: 20px;
}
}
.timeline {
position: relative;
height: 150px;
margin: 0 auto;
max-width: 1200px;
min-width: 600px;
}
@media (max-width: 768px) {
.timeline {
height: 120px;
min-width: 500px;
}
}
.timeline-line {
position: absolute;
top: 50%;
left: 0;
right: 0;
height: 2px;
background: #e0e0e0;
border-radius: 2px;
}
.today-marker {
position: absolute;
top: 50%;
transform: translateX(-50%);
z-index: 100;
}
.today-arrow {
font-size: 28px;
color: #e74c3c;
text-align: center;
line-height: 0;
margin-bottom: -6px;
}
@media (max-width: 768px) {
.today-arrow {
font-size: 24px;
}
}
.today-label {
background: #e74c3c;
color: white;
padding: 4px 10px;
border-radius: 4px;
font-weight: 600;
font-size: 11px;
text-align: center;
white-space: nowrap;
}
@media (max-width: 768px) {
.today-label {
font-size: 10px;
padding: 3px 8px;
}
}
.timeline-day-group {
position: absolute;
top: 0;
transform: translateX(-50%);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
border: 1px solid #e8e8e8;
border-radius: 6px;
overflow: hidden;
}
.upcoming-list--scrollable {
max-height: calc(5 * 49px);
overflow-y: auto;
}
.upcoming-row {
display: grid;
grid-template-columns: 1fr auto auto;
align-items: center; align-items: center;
gap: 6px; gap: 16px;
padding: 12px 16px;
border-bottom: 1px solid #f0f0f0;
background: white;
transition: background 0.15s;
} }
.timeline-date { .upcoming-row:last-child {
font-size: 10px; border-bottom: none;
color: #999;
font-weight: 500;
} }
@media (max-width: 768px) { .upcoming-row:hover {
.timeline-date { background: #fafafa;
font-size: 9px;
}
} }
.timeline-tasks { .upcoming-name {
display: flex; position: relative;
flex-direction: column; min-width: 0;
align-items: flex-start; cursor: default;
gap: 4px;
margin-top: 40px;
} }
@media (max-width: 768px) { .upcoming-name-text {
.timeline-tasks { display: block;
margin-top: 32px;
}
}
.timeline-task {
background: #555;
color: white;
padding: 4px 8px;
border-radius: 8px;
font-size: 10px;
white-space: nowrap; white-space: nowrap;
max-width: 100px;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); font-size: 14px;
position: relative; color: #111;
}
.upcoming-badge {
font-size: 11px;
color: #888;
white-space: nowrap;
border: 1px solid #e8e8e8;
padding: 2px 8px;
border-radius: 10px;
}
.upcoming-due {
font-size: 13px;
color: #111;
font-weight: 500;
white-space: nowrap;
min-width: 80px;
text-align: right;
}
.no-upcoming {
color: #aaa;
font-size: 14px;
font-style: italic;
padding: 16px 0;
}
/* shared tooltip style */
.name-tooltip {
position: absolute;
bottom: calc(100% + 6px);
left: 0;
background: #111;
color: white;
font-size: 12px;
padding: 6px 10px;
border-radius: 4px;
white-space: nowrap;
max-width: 280px;
overflow: hidden;
text-overflow: ellipsis;
z-index: 100;
pointer-events: none;
}
.name-tooltip::after {
content: '';
position: absolute;
top: 100%;
left: 12px;
border: 5px solid transparent;
border-top-color: #111;
} }
@media (max-width: 768px) { @media (max-width: 768px) {
.timeline-task { .upcoming-row {
font-size: 9px; grid-template-columns: 1fr auto;
padding: 3px 6px; gap: 4px 12px;
max-width: 80px; padding: 10px 12px;
}
.upcoming-badge {
display: none;
}
.upcoming-due {
font-size: 12px;
min-width: auto;
text-align: right;
} }
} }
.timeline-task.essential {
background: #2c3e50;
}

View File

@ -1,101 +1,78 @@
import { useState } from 'react';
import type { Task } from '../types'; import type { Task } from '../types';
import { calculateNextDueDate, formatDate } from '../utils/taskUtils'; import { calculateNextDueDate } from '../utils/taskUtils';
import './Timeline.css'; import './Timeline.css';
interface TimelineProps { interface TimelineProps {
tasks: Task[]; tasks: Task[];
} }
const MAX_VISIBLE = 5;
export default function Timeline({ tasks }: TimelineProps) { export default function Timeline({ tasks }: TimelineProps) {
const today = new Date(); const today = new Date();
today.setHours(0, 0, 0, 0); today.setHours(0, 0, 0, 0);
// Timeline range: 15 days past, 30 days future const upcomingItems = tasks
const startDate = new Date(today); .map(task => {
startDate.setDate(startDate.getDate() - 15); const dueDate = calculateNextDueDate(task);
if (!dueDate) return null;
const due = new Date(dueDate);
due.setHours(0, 0, 0, 0);
if (due <= today) return null;
return { task, dueDate: due };
})
.filter(Boolean)
.sort((a, b) => a!.dueDate.getTime() - b!.dueDate.getTime()) as { task: Task; dueDate: Date }[];
const endDate = new Date(today); const hasMore = upcomingItems.length > MAX_VISIBLE;
endDate.setDate(endDate.getDate() + 30);
// Calculate positions for tasks on timeline const formatDueDate = (date: Date): string => {
const totalDays = 45; // 15 past + 30 future const diff = Math.ceil((date.getTime() - today.getTime()) / (1000 * 60 * 60 * 24));
const todayPosition = (15 / totalDays) * 100; // 33.33% from left if (diff === 1) return 'Tomorrow';
if (diff <= 7) return `In ${diff} days`;
const taskPositions = tasks.map(task => { return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
const dueDate = calculateNextDueDate(task); };
if (!dueDate) return null;
// Only show tasks within timeline range
if (dueDate < startDate || dueDate > endDate) return null;
const daysDiff = Math.floor((dueDate.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24));
const position = (daysDiff / totalDays) * 100;
return {
task,
position,
dueDate,
};
}).filter(Boolean) as { task: Task; position: number; dueDate: Date }[];
// Group tasks by day
const tasksByDay = taskPositions.reduce((acc, item) => {
const dateKey = item.dueDate.toDateString();
if (!acc[dateKey]) acc[dateKey] = [];
acc[dateKey].push(item);
return acc;
}, {} as Record<string, typeof taskPositions>);
// Sort tasks within each day by priority
Object.values(tasksByDay).forEach(dayTasks => {
dayTasks.sort((a, b) => {
if (a.task.priority === 'ESSENTIAL' && b.task.priority !== 'ESSENTIAL') return -1;
if (a.task.priority !== 'ESSENTIAL' && b.task.priority === 'ESSENTIAL') return 1;
return 0;
});
});
return ( return (
<div className="timeline-container"> <section className="upcoming-section">
<div className="timeline"> <h2 className="section-title">What the future holds</h2>
<div className="timeline-line"></div> {upcomingItems.length === 0 ? (
<p className="no-upcoming">No upcoming tasks scheduled</p>
{/* TODAY marker */} ) : (
<div className="today-marker" style={{ left: `${todayPosition}%` }}> <div className={`upcoming-list${hasMore ? ' upcoming-list--scrollable' : ''}`}>
<div className="today-arrow"></div> {upcomingItems.map(({ task, dueDate }) => (
<div className="today-label">TODAY</div> <UpcomingRow key={task.id} task={task} dueLabel={formatDueDate(dueDate)} />
))}
</div> </div>
)}
</section>
);
}
{/* Tasks */} interface UpcomingRowProps {
{Object.entries(tasksByDay).map(([dateKey, dayTasks]) => { task: Task;
const avgPosition = dayTasks.reduce((sum, item) => sum + item.position, 0) / dayTasks.length; dueLabel: string;
}
return ( function UpcomingRow({ task, dueLabel }: UpcomingRowProps) {
<div const [tooltipVisible, setTooltipVisible] = useState(false);
key={dateKey}
className="timeline-day-group" return (
style={{ left: `${avgPosition}%` }} <div className="upcoming-row">
> <span
<div className="timeline-date">{formatDate(dayTasks[0].dueDate)}</div> className="upcoming-name"
<div className="timeline-tasks"> onClick={() => setTooltipVisible(v => !v)}
{dayTasks.map(({ task }, index) => ( onMouseEnter={() => setTooltipVisible(true)}
<div onMouseLeave={() => setTooltipVisible(false)}
key={task.id} >
className={`timeline-task ${task.priority.toLowerCase()}`} <span className="upcoming-name-text">{task.name}</span>
style={{ {tooltipVisible && <span className="name-tooltip">{task.name}</span>}
marginLeft: `${index * 8}px`, </span>
zIndex: dayTasks.length - index <span className="upcoming-badge">
}} {task.priority === 'ESSENTIAL' ? 'Essential' : 'When I have time'}
title={task.name} </span>
> <span className="upcoming-due">{dueLabel}</span>
{task.name}
</div>
))}
</div>
</div>
);
})}
</div>
</div> </div>
); );
} }

View File

@ -1,13 +1,112 @@
.todays-tasks { .todays-tasks {
width: 100%; width: 100%;
max-width: 800px;
margin: 0 auto 40px;
} }
.todays-tasks h2 { .task-section {
font-size: 24px; margin-bottom: 36px;
margin-bottom: 20px; }
color: #2c3e50;
.count-badge {
font-size: 13px;
font-weight: 500;
color: #888;
background: #f0f0f0;
border-radius: 10px;
padding: 1px 8px;
}
.toggle-icon {
font-size: 12px;
color: #999;
margin-left: auto;
}
.task-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
}
@media (max-width: 900px) {
.task-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 540px) {
.task-grid {
grid-template-columns: 1fr;
}
}
.task-card {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 12px;
border: 1px solid #e8e8e8;
border-radius: 4px;
background: white;
min-width: 0;
transition: border-color 0.15s;
}
.task-card:hover {
border-color: #bbb;
}
.task-card-name {
flex: 1;
min-width: 0;
position: relative;
cursor: default;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-size: 14px;
color: #111;
}
.task-card-actions {
display: flex;
gap: 4px;
flex-shrink: 0;
}
.action-btn {
width: 28px;
height: 28px;
border: 1px solid #e8e8e8;
border-radius: 3px;
font-size: 14px;
cursor: pointer;
background: white;
color: #555;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.15s;
padding: 0;
}
.action-btn:hover {
background: #111;
border-color: #111;
color: white;
}
@media (max-width: 768px) {
.action-btn {
width: 34px;
height: 34px;
font-size: 16px;
}
}
.no-tasks {
color: #aaa;
font-size: 14px;
font-style: italic;
} }
@media (max-width: 768px) { @media (max-width: 768px) {
@ -17,162 +116,4 @@
} }
} }
.task-section {
margin-bottom: 24px;
}
@media (max-width: 768px) {
.task-section {
margin-bottom: 16px;
}
}
.priority-header {
font-size: 16px;
font-weight: 600;
padding: 10px 14px;
border-radius: 6px;
margin-bottom: 10px;
display: flex;
justify-content: space-between;
align-items: center;
background: #f5f5f5;
color: #555;
border-left: 3px solid #ddd;
}
@media (max-width: 768px) {
.priority-header {
font-size: 14px;
padding: 8px 12px;
}
}
.priority-header.essential {
background: #fafafa;
color: #2c3e50;
border-left-color: #e74c3c;
}
.priority-header.when-i-have-time {
background: #f5f5f5;
color: #666;
border-left-color: #bbb;
}
.priority-header.clickable {
cursor: pointer;
user-select: none;
transition: background 0.2s;
}
.priority-header.clickable:hover {
background: #efefef;
}
.toggle-icon {
font-size: 12px;
margin-left: 8px;
color: #999;
}
.task-list {
display: flex;
flex-direction: column;
gap: 8px;
}
@media (max-width: 768px) {
.task-list {
gap: 6px;
}
}
.task-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
background: white;
border: 1px solid #e0e0e0;
border-radius: 6px;
transition: all 0.2s;
}
@media (max-width: 768px) {
.task-item {
padding: 10px 12px;
}
}
.task-item:hover {
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
border-color: #ccc;
}
.task-name {
font-size: 15px;
color: #2c3e50;
flex: 1;
margin-right: 12px;
}
@media (max-width: 768px) {
.task-name {
font-size: 14px;
}
}
.task-actions {
display: flex;
gap: 6px;
flex-shrink: 0;
}
.action-btn {
width: 32px;
height: 32px;
border: 1px solid #e0e0e0;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
justify-content: center;
background: white;
color: #666;
}
@media (max-width: 768px) {
.action-btn {
width: 36px;
height: 36px;
}
}
.complete-btn:hover {
background: #f5f5f5;
border-color: #2c3e50;
color: #2c3e50;
}
.renew-btn:hover {
background: #f5f5f5;
border-color: #666;
color: #2c3e50;
}
.no-tasks {
color: #999;
font-style: italic;
padding: 12px 16px;
font-size: 14px;
}
@media (max-width: 768px) {
.no-tasks {
font-size: 13px;
padding: 10px 12px;
}
}

View File

@ -15,17 +15,15 @@ export default function TodaysTasks({ tasks, onComplete, onRenew }: TodaysTasksP
return ( return (
<div className="todays-tasks"> <div className="todays-tasks">
<h2>Today's Tasks</h2>
{/* Essential Tasks */} <section className="task-section">
<div className="task-section"> <h2 className="section-title">Need to do</h2>
<h3 className="priority-header essential">Essential</h3>
{essentialTasks.length === 0 ? ( {essentialTasks.length === 0 ? (
<p className="no-tasks">No essential tasks for today</p> <p className="no-tasks">Nothing essential for today</p>
) : ( ) : (
<div className="task-list"> <div className="task-grid">
{essentialTasks.map(task => ( {essentialTasks.map(task => (
<TaskItem <TaskCard
key={task.id} key={task.id}
task={task} task={task}
onComplete={onComplete} onComplete={onComplete}
@ -34,58 +32,68 @@ export default function TodaysTasks({ tasks, onComplete, onRenew }: TodaysTasksP
))} ))}
</div> </div>
)} )}
</div> </section>
{/* When I Have Time Tasks */} <section className="task-section">
<div className="task-section"> <h2
<h3 className="section-title section-title--clickable"
className="priority-header when-i-have-time clickable"
onClick={() => setShowWhenIHaveTime(!showWhenIHaveTime)} onClick={() => setShowWhenIHaveTime(!showWhenIHaveTime)}
> >
When I have time ({whenIHaveTimeTasks.length}) When I have time
<span className="toggle-icon">{showWhenIHaveTime ? '▼' : '▶'}</span> <span className="count-badge">{whenIHaveTimeTasks.length}</span>
</h3> <span className="toggle-icon">{showWhenIHaveTime ? '▾' : '▸'}</span>
</h2>
{showWhenIHaveTime && ( {showWhenIHaveTime && (
<div className="task-list"> whenIHaveTimeTasks.length === 0 ? (
{whenIHaveTimeTasks.length === 0 ? ( <p className="no-tasks">Nothing here</p>
<p className="no-tasks">No tasks in this category</p> ) : (
) : ( <div className="task-grid">
whenIHaveTimeTasks.map(task => ( {whenIHaveTimeTasks.map(task => (
<TaskItem <TaskCard
key={task.id} key={task.id}
task={task} task={task}
onComplete={onComplete} onComplete={onComplete}
onRenew={onRenew} onRenew={onRenew}
/> />
)) ))}
)} </div>
</div> )
)} )}
</div> </section>
</div> </div>
); );
} }
interface TaskItemProps { interface TaskCardProps {
task: Task; task: Task;
onComplete: (taskId: number) => void; onComplete: (taskId: number) => void;
onRenew: (taskId: number) => void; onRenew: (taskId: number) => void;
} }
function TaskItem({ task, onComplete, onRenew }: TaskItemProps) { function TaskCard({ task, onComplete, onRenew }: TaskCardProps) {
const [tooltipVisible, setTooltipVisible] = useState(false);
return ( return (
<div className="task-item"> <div className="task-card">
<span className="task-name">{task.name}</span> <span
<div className="task-actions"> className="task-card-name"
onClick={() => setTooltipVisible(v => !v)}
onMouseEnter={() => setTooltipVisible(true)}
onMouseLeave={() => setTooltipVisible(false)}
>
{task.name}
{tooltipVisible && <span className="name-tooltip">{task.name}</span>}
</span>
<div className="task-card-actions">
<button <button
className="action-btn complete-btn" className="action-btn"
onClick={() => onComplete(task.id)} onClick={() => onComplete(task.id)}
title="Mark as completed" title="Mark as completed"
> >
</button> </button>
<button <button
className="action-btn renew-btn" className="action-btn"
onClick={() => onRenew(task.id)} onClick={() => onRenew(task.id)}
title="Renew task" title="Renew task"
> >

View File

@ -1,47 +1,15 @@
:root { :root {
--text: #6b6375;
--text-h: #08060d;
--bg: #fff;
--border: #e5e4e7;
--code-bg: #f4f3ec;
--accent: #aa3bff;
--accent-bg: rgba(170, 59, 255, 0.1);
--accent-border: rgba(170, 59, 255, 0.5);
--shadow:
rgba(0, 0, 0, 0.1) 0 10px 15px -3px, rgba(0, 0, 0, 0.05) 0 4px 6px -2px;
--sans: system-ui, 'Segoe UI', Roboto, sans-serif; --sans: system-ui, 'Segoe UI', Roboto, sans-serif;
--heading: system-ui, 'Segoe UI', Roboto, sans-serif;
--mono: ui-monospace, Consolas, monospace; --mono: ui-monospace, Consolas, monospace;
font: 18px/145% var(--sans); font: 16px/150% var(--sans);
letter-spacing: 0.18px; color: #111;
color-scheme: light dark; background: #fff;
color: var(--text); color-scheme: light;
background: var(--bg);
font-synthesis: none; font-synthesis: none;
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
@media (max-width: 1024px) {
font-size: 16px;
}
}
@media (prefers-color-scheme: dark) {
:root {
--text: #9ca3af;
--text-h: #f3f4f6;
--bg: #16171d;
--border: #2e303a;
--code-bg: #1f2028;
--accent: #c084fc;
--accent-bg: rgba(192, 132, 252, 0.15);
--accent-border: rgba(192, 132, 252, 0.5);
--shadow:
rgba(0, 0, 0, 0.4) 0 10px 15px -3px, rgba(0, 0, 0, 0.25) 0 4px 6px -2px;
}
} }
* { * {
@ -50,42 +18,18 @@
body { body {
margin: 0; margin: 0;
background: #fff;
} }
#root { #root {
min-height: 100svh; min-height: 100svh;
background: #fff;
} }
h1, h1, h2, h3 {
h2 {
font-family: var(--heading);
font-weight: 500;
color: var(--text-h);
}
h1 {
font-size: 48px;
letter-spacing: -1.5px;
margin: 0; margin: 0;
@media (max-width: 768px) {
font-size: 32px;
}
}
h2 {
font-size: 24px;
margin: 0 0 8px;
} }
p { p {
margin: 0; margin: 0;
} }
code {
font-family: var(--mono);
font-size: 14px;
padding: 2px 6px;
border-radius: 4px;
background: var(--code-bg);
color: var(--text-h);
}