Compare commits

..

No commits in common. "4e9194495662d4dec611c64ccf1694e17a9e1305" and "e51b09f9c537ff4203409629bbc84f65198a303d" have entirely different histories.

12 changed files with 117 additions and 180 deletions

View File

@ -1,5 +1,5 @@
import { useState, useRef, useEffect, useMemo, useCallback } from 'react'; import { useState, useRef, useEffect, useMemo, useCallback } from 'react';
import { useMediaQuery, DESKTOP } from '@/hooks/useMediaQuery'; import { useMediaQuery } from '@/hooks/useMediaQuery';
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { toast } from 'sonner'; import { toast } from 'sonner';
@ -20,7 +20,6 @@ import { Select } from '@/components/ui/select';
import { Sheet, SheetContent, SheetClose } from '@/components/ui/sheet'; import { Sheet, SheetContent, SheetClose } from '@/components/ui/sheet';
import CalendarSidebar from './CalendarSidebar'; import CalendarSidebar from './CalendarSidebar';
import EventDetailPanel from './EventDetailPanel'; import EventDetailPanel from './EventDetailPanel';
import MobileDetailOverlay from '@/components/shared/MobileDetailOverlay';
import type { CreateDefaults } from './EventDetailPanel'; import type { CreateDefaults } from './EventDetailPanel';
type CalendarView = 'dayGridMonth' | 'timeGridWeek' | 'timeGridDay'; type CalendarView = 'dayGridMonth' | 'timeGridWeek' | 'timeGridDay';
@ -165,7 +164,7 @@ export default function CalendarPage() {
const panelOpen = panelMode !== 'closed'; const panelOpen = panelMode !== 'closed';
// Track desktop breakpoint to prevent dual EventDetailPanel mount // Track desktop breakpoint to prevent dual EventDetailPanel mount
const isDesktop = useMediaQuery(DESKTOP); const isDesktop = useMediaQuery('(min-width: 1024px)');
const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false); const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
// Continuously resize calendar during panel open/close CSS transition // Continuously resize calendar during panel open/close CSS transition
@ -640,7 +639,14 @@ export default function CalendarPage() {
{/* Mobile detail panel overlay */} {/* Mobile detail panel overlay */}
{panelOpen && !isDesktop && ( {panelOpen && !isDesktop && (
<MobileDetailOverlay open onClose={handlePanelClose} className="sm:max-w-[400px]"> <div
className="fixed inset-0 z-50 bg-background/80 backdrop-blur-sm"
onClick={handlePanelClose}
>
<div
className="fixed inset-y-0 right-0 w-full sm:w-[400px] bg-card border-l border-border shadow-lg overflow-y-auto"
onClick={(e) => e.stopPropagation()}
>
<EventDetailPanel <EventDetailPanel
event={panelMode === 'view' ? selectedEvent : null} event={panelMode === 'view' ? selectedEvent : null}
isCreating={panelMode === 'create'} isCreating={panelMode === 'create'}
@ -651,7 +657,8 @@ export default function CalendarPage() {
myPermission={selectedEventPermission} myPermission={selectedEventPermission}
isSharedEvent={selectedEventIsShared} isSharedEvent={selectedEventIsShared}
/> />
</MobileDetailOverlay> </div>
</div>
)} )}
</div> </div>
); );

View File

@ -1,5 +1,5 @@
import { useState, useMemo, useRef, useEffect } from 'react'; import { useState, useMemo, useRef, useEffect } from 'react';
import { useMediaQuery, DESKTOP } from '@/hooks/useMediaQuery'; import { useMediaQuery } from '@/hooks/useMediaQuery';
import { Plus, MapPin, Phone, Mail, Tag, AlignLeft } from 'lucide-react'; import { Plus, MapPin, Phone, Mail, Tag, AlignLeft } from 'lucide-react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { toast } from 'sonner'; import { toast } from 'sonner';
@ -17,11 +17,10 @@ import {
import { useTableVisibility } from '@/hooks/useTableVisibility'; import { useTableVisibility } from '@/hooks/useTableVisibility';
import { useCategoryOrder } from '@/hooks/useCategoryOrder'; import { useCategoryOrder } from '@/hooks/useCategoryOrder';
import LocationForm from './LocationForm'; import LocationForm from './LocationForm';
import MobileDetailOverlay from '@/components/shared/MobileDetailOverlay';
export default function LocationsPage() { export default function LocationsPage() {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const isDesktop = useMediaQuery(DESKTOP); const isDesktop = useMediaQuery('(min-width: 1024px)');
const [selectedLocationId, setSelectedLocationId] = useState<number | null>(null); const [selectedLocationId, setSelectedLocationId] = useState<number | null>(null);
const [showForm, setShowForm] = useState(false); const [showForm, setShowForm] = useState(false);
@ -388,9 +387,17 @@ export default function LocationsPage() {
{/* Mobile detail panel overlay */} {/* Mobile detail panel overlay */}
{panelOpen && selectedLocation && !isDesktop && ( {panelOpen && selectedLocation && !isDesktop && (
<MobileDetailOverlay open={true} onClose={() => setSelectedLocationId(null)}> <div
className="fixed inset-0 z-50 bg-background/80 backdrop-blur-sm"
onClick={() => setSelectedLocationId(null)}
>
<div
className="fixed inset-y-0 right-0 w-full sm:w-[400px] bg-card border-l border-border shadow-lg"
onClick={(e) => e.stopPropagation()}
>
{renderPanel()} {renderPanel()}
</MobileDetailOverlay> </div>
</div>
)} )}
{showForm && ( {showForm && (

View File

@ -1,5 +1,5 @@
import { useState, useMemo, useRef, useEffect } from 'react'; import { useState, useMemo, useRef, useEffect } from 'react';
import { useMediaQuery, DESKTOP } from '@/hooks/useMediaQuery'; import { useMediaQuery } from '@/hooks/useMediaQuery';
import { Plus, Users, Star, Cake, Phone, Mail, MapPin, Tag, Building2, Briefcase, AlignLeft, Ghost, ChevronDown, Unlink, Link2, User2 } from 'lucide-react'; import { Plus, Users, Star, Cake, Phone, Mail, MapPin, Tag, Building2, Briefcase, AlignLeft, Ghost, ChevronDown, Unlink, Link2, User2 } from 'lucide-react';
import type { LucideIcon } from 'lucide-react'; import type { LucideIcon } from 'lucide-react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
@ -27,7 +27,6 @@ import PersonForm from './PersonForm';
import ConnectionSearch from '@/components/connections/ConnectionSearch'; import ConnectionSearch from '@/components/connections/ConnectionSearch';
import ConnectionRequestCard from '@/components/connections/ConnectionRequestCard'; import ConnectionRequestCard from '@/components/connections/ConnectionRequestCard';
import { useConnections } from '@/hooks/useConnections'; import { useConnections } from '@/hooks/useConnections';
import MobileDetailOverlay from '@/components/shared/MobileDetailOverlay';
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// StatCounter — inline helper // StatCounter — inline helper
@ -216,7 +215,7 @@ const panelFields: PanelField[] = [
export default function PeoplePage() { export default function PeoplePage() {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const tableContainerRef = useRef<HTMLDivElement>(null); const tableContainerRef = useRef<HTMLDivElement>(null);
const isDesktop = useMediaQuery(DESKTOP); const isDesktop = useMediaQuery('(min-width: 1024px)');
const [selectedPersonId, setSelectedPersonId] = useState<number | null>(null); const [selectedPersonId, setSelectedPersonId] = useState<number | null>(null);
const [showForm, setShowForm] = useState(false); const [showForm, setShowForm] = useState(false);
@ -777,9 +776,17 @@ export default function PeoplePage() {
{/* Mobile detail panel overlay */} {/* Mobile detail panel overlay */}
{panelOpen && selectedPerson && !isDesktop && ( {panelOpen && selectedPerson && !isDesktop && (
<MobileDetailOverlay open={true} onClose={() => setSelectedPersonId(null)}> <div
className="fixed inset-0 z-50 bg-background/80 backdrop-blur-sm"
onClick={() => setSelectedPersonId(null)}
>
<div
className="fixed inset-y-0 right-0 w-full sm:w-[400px] bg-card border-l border-border shadow-lg"
onClick={(e) => e.stopPropagation()}
>
{renderPanel()} {renderPanel()}
</MobileDetailOverlay> </div>
</div>
)} )}
{showForm && ( {showForm && (

View File

@ -154,7 +154,7 @@ export default function KanbanBoard({
}: KanbanBoardProps) { }: KanbanBoardProps) {
const sensors = useSensors( const sensors = useSensors(
useSensor(PointerSensor, { activationConstraint: { distance: 5 } }) , useSensor(PointerSensor, { activationConstraint: { distance: 5 } }) ,
useSensor(TouchSensor, { activationConstraint: { delay: 200, tolerance: 8 } }) useSensor(TouchSensor, { activationConstraint: { delay: 200, tolerance: 5 } })
); );
// Subtask view is driven by kanbanParentTask (decoupled from selected task) // Subtask view is driven by kanbanParentTask (decoupled from selected task)

View File

@ -1,5 +1,5 @@
import { useState, useMemo, useCallback } from 'react'; import { useState, useMemo, useCallback } from 'react';
import { useMediaQuery, DESKTOP } from '@/hooks/useMediaQuery'; import { useMediaQuery } from '@/hooks/useMediaQuery';
import { useParams, useNavigate } from 'react-router-dom'; import { useParams, useNavigate } from 'react-router-dom';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { toast } from 'sonner'; import { toast } from 'sonner';
@ -39,7 +39,6 @@ import KanbanBoard from './KanbanBoard';
import TaskForm from './TaskForm'; import TaskForm from './TaskForm';
import ProjectForm from './ProjectForm'; import ProjectForm from './ProjectForm';
import { statusColors, statusLabels } from './constants'; import { statusColors, statusLabels } from './constants';
import MobileDetailOverlay from '@/components/shared/MobileDetailOverlay';
type SortMode = 'manual' | 'priority' | 'due_date'; type SortMode = 'manual' | 'priority' | 'due_date';
type ViewMode = 'list' | 'kanban'; type ViewMode = 'list' | 'kanban';
@ -259,7 +258,7 @@ export default function ProjectDetail() {
} }
}, [topLevelTasks, sortMode, sortSubtasks]); }, [topLevelTasks, sortMode, sortSubtasks]);
const isDesktop = useMediaQuery(DESKTOP); const isDesktop = useMediaQuery('(min-width: 1024px)');
const selectedTask = useMemo(() => { const selectedTask = useMemo(() => {
if (!selectedTaskId) return null; if (!selectedTaskId) return null;
@ -654,7 +653,8 @@ export default function ProjectDetail() {
{/* Mobile: show detail panel as overlay when task selected on small screens */} {/* Mobile: show detail panel as overlay when task selected on small screens */}
{selectedTaskId && selectedTask && !isDesktop && ( {selectedTaskId && selectedTask && !isDesktop && (
<MobileDetailOverlay open={true} onClose={() => setSelectedTaskId(null)}> <div className="fixed inset-0 z-50 bg-background/80 backdrop-blur-sm">
<div className="fixed inset-y-0 right-0 w-full sm:w-[400px] bg-card border-l border-border shadow-lg">
<div className="flex items-center justify-between px-4 py-3 border-b border-border"> <div className="flex items-center justify-between px-4 py-3 border-b border-border">
<span className="text-sm font-medium text-muted-foreground">Task Details</span> <span className="text-sm font-medium text-muted-foreground">Task Details</span>
<Button <Button
@ -675,7 +675,8 @@ export default function ProjectDetail() {
onSelectTask={setSelectedTaskId} onSelectTask={setSelectedTaskId}
/> />
</div> </div>
</MobileDetailOverlay> </div>
</div>
)} )}
{showTaskForm && ( {showTaskForm && (

View File

@ -1,5 +1,5 @@
import { useState, useMemo, useEffect } from 'react'; import { useState, useMemo, useEffect } from 'react';
import { useMediaQuery, DESKTOP } from '@/hooks/useMediaQuery'; import { useMediaQuery } from '@/hooks/useMediaQuery';
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
import { Plus, Bell, BellOff, AlertCircle, Search } from 'lucide-react'; import { Plus, Bell, BellOff, AlertCircle, Search } from 'lucide-react';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
@ -13,7 +13,6 @@ import { Card, CardContent } from '@/components/ui/card';
import { ListSkeleton } from '@/components/ui/skeleton'; import { ListSkeleton } from '@/components/ui/skeleton';
import ReminderList from './ReminderList'; import ReminderList from './ReminderList';
import ReminderDetailPanel from './ReminderDetailPanel'; import ReminderDetailPanel from './ReminderDetailPanel';
import MobileDetailOverlay from '@/components/shared/MobileDetailOverlay';
const statusFilters = [ const statusFilters = [
{ value: 'active', label: 'Active' }, { value: 'active', label: 'Active' },
@ -26,7 +25,7 @@ type StatusFilter = (typeof statusFilters)[number]['value'];
export default function RemindersPage() { export default function RemindersPage() {
const location = useLocation(); const location = useLocation();
const isDesktop = useMediaQuery(DESKTOP); const isDesktop = useMediaQuery('(min-width: 1024px)');
// Panel state // Panel state
const [selectedReminderId, setSelectedReminderId] = useState<number | null>(null); const [selectedReminderId, setSelectedReminderId] = useState<number | null>(null);
@ -236,14 +235,22 @@ export default function RemindersPage() {
{/* Mobile detail panel overlay */} {/* Mobile detail panel overlay */}
{panelOpen && !isDesktop && ( {panelOpen && !isDesktop && (
<MobileDetailOverlay open={true} onClose={handlePanelClose}> <div
className="fixed inset-0 z-50 bg-background/80 backdrop-blur-sm"
onClick={handlePanelClose}
>
<div
className="fixed inset-y-0 right-0 w-full sm:w-[400px] bg-card border-l border-border shadow-lg"
onClick={(e) => e.stopPropagation()}
>
<ReminderDetailPanel <ReminderDetailPanel
reminder={panelMode === 'view' ? selectedReminder : null} reminder={panelMode === 'view' ? selectedReminder : null}
isCreating={panelMode === 'create'} isCreating={panelMode === 'create'}
onClose={handlePanelClose} onClose={handlePanelClose}
onSaved={panelMode === 'create' ? handlePanelClose : undefined} onSaved={panelMode === 'create' ? handlePanelClose : undefined}
/> />
</MobileDetailOverlay> </div>
</div>
)} )}
</div> </div>
); );

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { ArrowUpDown, ArrowUp, ArrowDown, ChevronsUpDown } from 'lucide-react'; import { ArrowUpDown, ArrowUp, ArrowDown } from 'lucide-react';
import type { VisibilityMode } from '@/hooks/useTableVisibility'; import type { VisibilityMode } from '@/hooks/useTableVisibility';
import { useMediaQuery, MOBILE } from '@/hooks/useMediaQuery'; import { useMediaQuery } from '@/hooks/useMediaQuery';
export interface ColumnDef<T> { export interface ColumnDef<T> {
key: string; key: string;
@ -134,30 +134,11 @@ export function EntityTable<T extends { id: number }>({
const visibleColumns = columns.filter((col) => isVisible(col.visibilityLevel, visibilityMode)); const visibleColumns = columns.filter((col) => isVisible(col.visibilityLevel, visibilityMode));
const colCount = visibleColumns.length; const colCount = visibleColumns.length;
const showPinnedSection = showPinned && pinnedRows.length > 0; const showPinnedSection = showPinned && pinnedRows.length > 0;
const isMobile = useMediaQuery(MOBILE); const isMobile = useMediaQuery('(max-width: 767px)');
const sortableColumns = columns.filter((col) => col.sortable);
if (isMobile && mobileCardRender) { if (isMobile && mobileCardRender) {
return ( return (
<div className="space-y-2"> <div className="space-y-2">
{sortableColumns.length > 0 && !loading && (
<div className="flex items-center gap-2 justify-end">
<ChevronsUpDown className="h-3.5 w-3.5 text-muted-foreground" />
<select
value={sortKey}
onChange={(e) => onSort(e.target.value)}
aria-label="Sort by"
className="h-7 rounded-md border border-border bg-card px-2 text-xs text-foreground"
>
{sortableColumns.map((col) => (
<option key={col.key} value={col.key}>
{col.label} {sortKey === col.key ? (sortDir === 'asc' ? '↑' : '↓') : ''}
</option>
))}
</select>
</div>
)}
{loading ? ( {loading ? (
Array.from({ length: 6 }).map((_, i) => ( Array.from({ length: 6 }).map((_, i) => (
<div key={i} className="animate-pulse rounded-lg bg-card border border-border p-4 h-20" /> <div key={i} className="animate-pulse rounded-lg bg-card border border-border p-4 h-20" />

View File

@ -1,67 +0,0 @@
import { useEffect, useRef } from 'react';
import { cn } from '@/lib/utils';
interface MobileDetailOverlayProps {
open: boolean;
onClose: () => void;
children: React.ReactNode;
className?: string;
}
/**
* Full-screen overlay for mobile detail panels.
* - Backdrop click closes the overlay
* - Escape key closes the overlay
* - Body scroll is locked while open
*/
export default function MobileDetailOverlay({
open,
onClose,
children,
className,
}: MobileDetailOverlayProps) {
// Stable ref to avoid re-registering listener on every render
const onCloseRef = useRef(onClose);
onCloseRef.current = onClose;
// Escape key handler
useEffect(() => {
if (!open) return;
const handler = (e: KeyboardEvent) => {
if (e.key === 'Escape') onCloseRef.current();
};
document.addEventListener('keydown', handler);
return () => document.removeEventListener('keydown', handler);
}, [open]);
// Body scroll lock
useEffect(() => {
if (!open) return;
const previous = document.body.style.overflow;
document.body.style.overflow = 'hidden';
return () => {
document.body.style.overflow = previous;
};
}, [open]);
if (!open) return null;
return (
<div
role="dialog"
aria-modal="true"
className="fixed inset-0 z-50 bg-background/80 backdrop-blur-sm animate-fade-in"
onClick={onClose}
>
<div
className={cn(
'absolute right-0 top-0 h-full w-full sm:max-w-md bg-card border-l border-border shadow-xl overflow-y-auto',
className,
)}
onClick={(e) => e.stopPropagation()}
>
{children}
</div>
</div>
);
}

View File

@ -1,5 +1,5 @@
import { useState, useMemo, useEffect } from 'react'; import { useState, useMemo, useEffect } from 'react';
import { useMediaQuery, DESKTOP } from '@/hooks/useMediaQuery'; import { useMediaQuery } from '@/hooks/useMediaQuery';
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
import { Plus, CheckSquare, CheckCircle2, AlertCircle } from 'lucide-react'; import { Plus, CheckSquare, CheckCircle2, AlertCircle } from 'lucide-react';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
@ -11,7 +11,6 @@ import { Select } from '@/components/ui/select';
import { Card, CardContent } from '@/components/ui/card'; import { Card, CardContent } from '@/components/ui/card';
import { ListSkeleton } from '@/components/ui/skeleton'; import { ListSkeleton } from '@/components/ui/skeleton';
import { CategoryFilterBar } from '@/components/shared'; import { CategoryFilterBar } from '@/components/shared';
import MobileDetailOverlay from '@/components/shared/MobileDetailOverlay';
import { useCategoryOrder } from '@/hooks/useCategoryOrder'; import { useCategoryOrder } from '@/hooks/useCategoryOrder';
import TodoList from './TodoList'; import TodoList from './TodoList';
import TodoDetailPanel from './TodoDetailPanel'; import TodoDetailPanel from './TodoDetailPanel';
@ -27,7 +26,7 @@ const priorityFilters = [
export default function TodosPage() { export default function TodosPage() {
const location = useLocation(); const location = useLocation();
const isDesktop = useMediaQuery(DESKTOP); const isDesktop = useMediaQuery('(min-width: 1024px)');
// Panel state // Panel state
const [selectedTodoId, setSelectedTodoId] = useState<number | null>(null); const [selectedTodoId, setSelectedTodoId] = useState<number | null>(null);
@ -271,14 +270,22 @@ export default function TodosPage() {
{/* Mobile detail panel overlay */} {/* Mobile detail panel overlay */}
{panelOpen && !isDesktop && ( {panelOpen && !isDesktop && (
<MobileDetailOverlay open={true} onClose={handlePanelClose}> <div
className="fixed inset-0 z-50 bg-background/80 backdrop-blur-sm"
onClick={handlePanelClose}
>
<div
className="fixed inset-y-0 right-0 w-full sm:w-[400px] bg-card border-l border-border shadow-lg"
onClick={(e) => e.stopPropagation()}
>
<TodoDetailPanel <TodoDetailPanel
todo={panelMode === 'view' ? selectedTodo : null} todo={panelMode === 'view' ? selectedTodo : null}
isCreating={panelMode === 'create'} isCreating={panelMode === 'create'}
onClose={handlePanelClose} onClose={handlePanelClose}
onSaved={panelMode === 'create' ? handlePanelClose : undefined} onSaved={panelMode === 'create' ? handlePanelClose : undefined}
/> />
</MobileDetailOverlay> </div>
</div>
)} )}
</div> </div>
); );

View File

@ -2,7 +2,7 @@ import * as React from 'react';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import { Calendar, ChevronLeft, ChevronRight, Clock } from 'lucide-react'; import { Calendar, ChevronLeft, ChevronRight, Clock } from 'lucide-react';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { useMediaQuery, MOBILE } from '@/hooks/useMediaQuery'; import { useMediaQuery } from '@/hooks/useMediaQuery';
// ── Browser detection (stable — checked once at module load) ── // ── Browser detection (stable — checked once at module load) ──
@ -128,7 +128,7 @@ const DatePicker = React.forwardRef<HTMLButtonElement, DatePickerProps>(
const blurTimeoutRef = React.useRef<ReturnType<typeof setTimeout>>(); const blurTimeoutRef = React.useRef<ReturnType<typeof setTimeout>>();
const [pos, setPos] = React.useState<{ top: number; left: number }>({ top: 0, left: 0 }); const [pos, setPos] = React.useState<{ top: number; left: number }>({ top: 0, left: 0 });
const isMobile = useMediaQuery(MOBILE); const isMobile = useMediaQuery('(max-width: 767px)');
React.useImperativeHandle(ref, () => triggerRef.current!); React.useImperativeHandle(ref, () => triggerRef.current!);

View File

@ -1,8 +1,5 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
export const MOBILE = '(max-width: 767px)';
export const DESKTOP = '(min-width: 1024px)';
export function useMediaQuery(query: string): boolean { export function useMediaQuery(query: string): boolean {
const [matches, setMatches] = useState(() => const [matches, setMatches] = useState(() =>
typeof window !== 'undefined' ? window.matchMedia(query).matches : false typeof window !== 'undefined' ? window.matchMedia(query).matches : false

View File

@ -292,12 +292,6 @@ form[data-submitted] input:invalid + button {
} }
/*
* Mobile font scaling overrides Tailwind text utilities below 768px.
* Applied via .mobile-scale class on <main> in AppLayout.tsx.
* These selectors rely on cascade order (loaded after Tailwind utilities).
* Arbitrary sizes like text-[9px] are NOT captured see W-03 override below.
*/
/* ── Global mobile content scaling ── */ /* ── Global mobile content scaling ── */
/* Scales down all page content text on mobile. Navbar and UMBRA title are excluded /* Scales down all page content text on mobile. Navbar and UMBRA title are excluded
because they live outside .mobile-scale and use their own sizing. */ because they live outside .mobile-scale and use their own sizing. */
@ -329,10 +323,6 @@ form[data-submitted] input:invalid + button {
.mobile-scale .text-3xl { .mobile-scale .text-3xl {
font-size: 1.375rem; /* 22px */ font-size: 1.375rem; /* 22px */
} }
/* W-03: Enforce 10px floor for arbitrary small sizes */
.mobile-scale [class*="text-\[9px\]"] {
font-size: 10px !important;
}
} }
/* ── Mobile touch optimisation ── */ /* ── Mobile touch optimisation ── */