Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,34 @@
<meta name="application-name" content="OpsNormal" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="theme-color" content="#0a0f0d" />
<link
rel="preload"
href="%BASE_URL%fonts/Inter-Latin.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<link
rel="preload"
href="%BASE_URL%fonts/IBMPlexMono-Regular-Latin.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<link
rel="preload"
href="%BASE_URL%fonts/IBMPlexMono-Medium-Latin.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<link
rel="preload"
href="%BASE_URL%fonts/IBMPlexMono-SemiBold-Latin.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<link rel="stylesheet" href="%BASE_URL%recovery-surfaces.css" />
<meta
name="description"
Expand Down
33 changes: 19 additions & 14 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -236,9 +236,12 @@ function App() {
<div className="grid gap-5 lg:gap-6">
<div className="flex max-w-4xl gap-4">
<div
className="hidden w-px shrink-0 self-stretch bg-ops-accent/45 sm:block"
className="hidden w-3 shrink-0 flex-col items-center self-stretch sm:flex"
aria-hidden="true"
/>
>
<span className="h-14 w-0.5 bg-ops-accent/60" />
<span className="mt-2 h-full w-px flex-1 bg-ops-border-struct" />
</div>
<div className="min-w-0">
<div className="flex flex-wrap items-center gap-3">
<span
Expand Down Expand Up @@ -367,19 +370,21 @@ function App() {
innerClassName="tactical-subpanel px-4 py-4 text-sm leading-6 text-ops-text-secondary"
>
<footer>
<div className="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
<div className="lg:max-w-2xl">
<p className="ops-eyebrow font-semibold text-ops-text-primary">
Boundary
</p>
<p className="mt-2">
OpsNormal is a personal status tracking tool. It is not a
medical device and does not diagnose, treat, cure, or prevent
any disease or condition. It does not provide medical or
psychological advice.
</p>
<div className="border-t border-ops-border-soft pt-5">
<div className="grid gap-5 lg:grid-cols-[minmax(0,1fr)_minmax(18rem,24rem)] lg:items-start">
<div className="lg:max-w-2xl">
<p className="ops-eyebrow font-semibold text-ops-text-primary">
Boundary
</p>
<p className="mt-2">
OpsNormal is a personal status tracking tool. It is not a
medical device and does not diagnose, treat, cure, or
prevent any disease or condition. It does not provide
medical or psychological advice.
</p>
</div>
<FooterProvenance />
</div>
<FooterProvenance />
</div>
</footer>
</NotchedFrame>
Expand Down
4 changes: 2 additions & 2 deletions src/components/DomainCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export function DomainCard({

const shellClassName = busy
? 'panel-shadow clip-notched ops-notch-panel-outer bg-ops-border-strong p-px'
: 'panel-shadow clip-notched ops-notch-panel-outer bg-ops-border-strong p-px transition-colors hover:bg-ops-accent/16 focus-within:bg-ops-accent/20';
: 'ops-domain-card panel-shadow group clip-notched ops-notch-panel-outer bg-ops-border-strong p-px transition-colors hover:bg-ops-accent/25 focus-within:bg-ops-accent/25';

useEffect(() => {
const pendingStatus = pendingKeyboardFocusStatusRef.current;
Expand Down Expand Up @@ -148,7 +148,7 @@ export function DomainCard({
<div className={shellClassName}>
<div
className={[
'clip-notched ops-notch-panel-inner tactical-panel flex min-h-[13rem] flex-col justify-between bg-[linear-gradient(180deg,rgba(255,255,255,0.025),transparent_28%),var(--color-ops-surface-2)] p-4 text-left sm:p-5',
'clip-notched ops-notch-panel-inner tactical-panel flex min-h-[14rem] transform-gpu flex-col justify-between bg-[linear-gradient(180deg,rgba(255,255,255,0.025),transparent_28%),var(--color-ops-surface-2)] p-4 text-left transition-transform duration-150 ease-out group-hover:-translate-y-px group-focus-within:-translate-y-px sm:p-5 xl:min-h-[15rem]',
spineClassName,
].join(' ')}
>
Expand Down
3 changes: 3 additions & 0 deletions src/components/FooterProvenance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ export function FooterProvenance() {
className="ops-provenance flex flex-col gap-3 sm:w-full sm:max-w-sm sm:self-end"
data-testid="footer-provenance"
>
<p className="ops-eyebrow font-semibold text-ops-text-primary">
Provenance
</p>
<dl className="ops-provenance-facts">
<div>
<dt>Build</dt>
Expand Down
4 changes: 2 additions & 2 deletions src/components/HeaderTelemetry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,11 @@ function TelemetryChip({
<span className="ops-eyebrow text-[10px] font-semibold text-ops-text-muted">
{label}
</span>
<span className="mt-1.5 text-lg leading-none font-semibold tracking-[0.06em] uppercase [font-variant-numeric:tabular-nums] sm:text-xl">
<span className="mt-2 text-2xl leading-none font-semibold tracking-[0.04em] uppercase [font-variant-numeric:tabular-nums] sm:text-3xl">
{value}
</span>
{detail ? (
<span className="mt-1.5 text-[10px] leading-4 tracking-[0.10em] text-ops-text-muted uppercase">
<span className="mt-2 text-[10px] leading-4 tracking-[0.10em] text-ops-text-muted uppercase">
{detail}
</span>
) : null}
Expand Down
13 changes: 7 additions & 6 deletions src/components/SectionCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ export function SectionCard({
emphasis = 'standard',
children,
}: SectionCardProps) {
const eyebrowClassName =
emphasis === 'primary'
? 'ops-eyebrow-strong ops-mono text-xs font-semibold text-ops-accent-muted'
: 'ops-eyebrow ops-mono text-xs font-semibold text-ops-text-muted';

return (
<div className="panel-shadow">
<div
Expand All @@ -24,12 +29,8 @@ export function SectionCard({
<section className="tactical-panel clip-notched ops-notch-panel-inner ops-section-surface p-6 sm:p-7 lg:p-8">
<div className="mb-5 flex flex-col gap-3 sm:flex-row sm:items-end sm:justify-between">
<div className="max-w-3xl">
{eyebrow ? (
<p className="ops-eyebrow-strong ops-mono text-xs font-semibold text-ops-accent-muted">
{eyebrow}
</p>
) : null}
<h2 className="ops-tracking-section mt-1 text-2xl font-semibold text-ops-text-primary uppercase sm:text-3xl">
{eyebrow ? <p className={eyebrowClassName}>{eyebrow}</p> : null}
<h2 className="ops-tracking-section mt-1 text-2xl font-semibold text-ops-text-primary uppercase sm:text-2xl lg:text-3xl">
{title}
</h2>
</div>
Expand Down
122 changes: 56 additions & 66 deletions src/features/checkin/TodayPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,51 +51,47 @@ function DayCompletionRollup({
const remainingCount = Math.max(totalCount - markedCount, 0);

return (
<div className="panel-shadow mb-4 clip-notched ops-notch-panel-outer bg-ops-border-strong p-px">
<div className="clip-notched ops-notch-panel-inner tactical-subpanel-strong ops-rollup-spine grid gap-4 p-4 sm:p-5 lg:grid-cols-[auto_minmax(0,1fr)_auto] lg:items-center">
<div className="min-w-[8rem]">
<p className="ops-eyebrow text-xs font-semibold text-ops-text-muted">
Daily roll-up
</p>
<p className="mt-2 text-3xl leading-none font-semibold tracking-[0.06em] text-ops-text-primary uppercase [font-variant-numeric:tabular-nums]">
{markedCount}/{totalCount}
</p>
</div>
<div className="text-sm leading-6 text-ops-text-secondary">
{isComplete
? 'All five sectors are marked for today.'
: `${remainingCount} sector${remainingCount === 1 ? '' : 's'} still open.`}
</div>
<div className="flex flex-wrap items-center gap-3 lg:justify-end">
<span
className={[
'ops-status-frame clip-notched ops-notch-chip inline-flex min-h-8 items-center border px-3 text-xs font-semibold tracking-[0.14em] uppercase',
isComplete ? 'ops-complete-badge' : '',
isComplete ? 'ops-status-nominal' : 'ops-status-unmarked',
].join(' ')}
>
{isComplete ? 'Complete' : 'Open'}
</span>
<div
className="flex items-center gap-1.5"
aria-label="Today sector status pips"
>
{SECTORS.map((sector, index) => (
<span
key={sector.id}
className={[
'clip-notched ops-notch-chip inline-flex h-4 w-7 items-center justify-center border',
getRollupPipClassName(statuses[index] ?? 'unmarked'),
].join(' ')}
title={sector.label}
>
<span className="sr-only">{sector.label}</span>
<span className="text-[9px] leading-none text-ops-text-primary/80">
<SectorGlyphMark sectorId={sector.id} />
</span>
<div className="panel-shadow mb-4 clip-notched ops-notch-panel-outer tactical-subpanel-strong ops-rollup-spine grid gap-4 p-4 sm:p-5 lg:grid-cols-[auto_minmax(0,1fr)_auto] lg:items-center">
<div className="min-w-[8rem]">
<p className="ops-eyebrow-mixed">Daily roll-up</p>
<p className="mt-2 text-3xl leading-none font-semibold tracking-[0.06em] text-ops-text-primary uppercase [font-variant-numeric:tabular-nums]">
{markedCount}/{totalCount}
</p>
</div>
<div className="text-sm leading-6 text-ops-text-secondary">
{isComplete
? 'All five sectors are marked for today.'
: `${remainingCount} sector${remainingCount === 1 ? '' : 's'} still open.`}
</div>
<div className="flex flex-wrap items-center gap-3 lg:justify-end">
<span
className={[
'ops-status-frame clip-notched ops-notch-chip inline-flex min-h-8 items-center border px-3 text-xs font-semibold tracking-[0.14em] uppercase',
isComplete ? 'ops-complete-badge' : '',
isComplete ? 'ops-status-nominal' : 'ops-status-unmarked',
].join(' ')}
>
{isComplete ? 'Complete' : 'Open'}
</span>
<div
className="flex items-center gap-1.5"
aria-label="Today sector status pips"
>
{SECTORS.map((sector, index) => (
<span
key={sector.id}
className={[
'clip-notched ops-notch-chip inline-flex h-4 w-7 items-center justify-center border',
getRollupPipClassName(statuses[index] ?? 'unmarked'),
].join(' ')}
title={sector.label}
>
<span className="sr-only">{sector.label}</span>
<span className="text-[9px] leading-none text-ops-text-primary/80">
<SectorGlyphMark sectorId={sector.id} />
</span>
))}
</div>
</span>
))}
</div>
</div>
</div>
Expand Down Expand Up @@ -222,31 +218,25 @@ export function TodayPanel({
) : null}

{!hasEntriesForToday ? (
<div className="mb-4 clip-notched ops-notch-panel-outer bg-ops-border-struct p-px">
<div className="clip-notched ops-notch-panel-inner tactical-subpanel-strong relative px-4 py-3">
<SectorGlyphConstellation />
<div className="relative z-10 max-w-2xl">
<p className="text-xs font-semibold tracking-[0.14em] text-ops-text-muted uppercase">
Awaiting first mark
</p>
<p className="mt-2 text-sm leading-6 text-ops-text-secondary">
No sectors are marked for today. Set one state below and the
daily roll-up will start tracking live progress.
</p>
</div>
<div className="panel-shadow mb-4 clip-notched ops-notch-panel-outer tactical-subpanel-strong relative px-4 py-3">
<SectorGlyphConstellation />
<div className="relative z-10 max-w-2xl">
<p className="ops-eyebrow-mixed">Awaiting first mark</p>
<p className="mt-2 text-sm leading-6 text-ops-text-secondary">
No sectors are marked for today. Set one state below and the daily
roll-up will start tracking live progress.
</p>
</div>
</div>
) : null}

<div className="mb-4 clip-notched ops-notch-panel-outer bg-ops-panel-border p-px">
<div
id={directSelectHintId}
className="clip-notched ops-notch-panel-inner tactical-subpanel px-4 py-3 text-sm leading-6 text-ops-text-secondary"
>
Choose a state directly. Arrow keys move inside the control group.
Unmarked means no status recorded for the day. Nominal and degraded
are deliberate check-ins, not automatic carry-forward.
</div>
<div
id={directSelectHintId}
className="mb-4 clip-notched ops-notch-panel-outer tactical-subpanel px-4 py-3 text-sm leading-6 text-ops-text-secondary"
>
Choose a state directly. Arrow keys move inside the control group.
Unmarked means no status recorded for the day. Nominal and degraded are
deliberate check-ins, not automatic carry-forward.
</div>

<DayCompletionRollup
Expand All @@ -256,7 +246,7 @@ export function TodayPanel({
totalCount={completion.totalCount}
/>

<div className="grid gap-4 sm:grid-cols-2 lg:gap-5 xl:grid-cols-5">
<div className="grid gap-4 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-5 lg:gap-5">
{SECTORS.map((sector, index) => (
<div key={sector.id}>
<DomainCard
Expand Down
34 changes: 12 additions & 22 deletions src/features/export/ExportBackupSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,14 @@ export function ExportBackupSection({
>
<div className="space-y-4">
<div className="grid gap-3 lg:grid-cols-[minmax(0,1fr)_auto]">
<div className="panel-shadow">
<div className="clip-notched ops-notch-panel-outer bg-ops-border-soft p-px">
<div className="clip-notched ops-notch-panel-inner bg-[linear-gradient(180deg,rgba(255,255,255,0.03),transparent_26%),var(--color-ops-surface-overlay)] p-4">
<p className="ops-eyebrow text-xs font-semibold text-ops-text-muted">
Backup posture
</p>
<p className="mt-2 text-sm leading-6 text-ops-text-secondary">
{backupStatus}
</p>
<p className="mt-2 text-xs leading-5 text-ops-text-muted">
Export is the primary safe recovery path. Run it routinely.
</p>
</div>
</div>
<div className="panel-shadow clip-notched ops-notch-panel-outer tactical-subpanel p-4">
<p className="ops-eyebrow-mixed">Backup posture</p>
<p className="mt-2 text-sm leading-6 text-ops-text-secondary">
{backupStatus}
</p>
<p className="mt-2 text-xs leading-5 text-ops-text-muted">
Export is the primary safe recovery path. Run it routinely.
</p>
</div>

<div className="flex flex-col gap-3 sm:flex-row lg:flex-col">
Expand All @@ -62,14 +56,10 @@ export function ExportBackupSection({
</div>
</div>

<div className="panel-shadow">
<div className="clip-notched ops-notch-panel-outer bg-ops-border-soft p-px">
<div className="clip-notched ops-notch-panel-inner bg-[linear-gradient(180deg,rgba(255,255,255,0.03),transparent_24%),var(--color-ops-surface-raised)] p-4 text-sm leading-6 text-ops-text-secondary">
Export produces the recovery file. Run it routinely, especially in
Safari on macOS and browser tabs on iPhone or iPad where
browser-managed storage can disappear.
</div>
</div>
<div className="panel-shadow clip-notched ops-notch-panel-outer tactical-subpanel-strong p-4 text-sm leading-6 text-ops-text-secondary">
Export produces the recovery file. Run it routinely, especially in
Safari on macOS and browser tabs on iPhone or iPad where
browser-managed storage can disappear.
</div>
</div>
</AccordionSection>
Expand Down
13 changes: 9 additions & 4 deletions src/features/export/exportPanelShared.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,12 @@ export function AccordionSection({
aria-expanded={isOpen}
aria-controls={panelId}
onClick={() => onToggle(sectionKey)}
className="ops-focus-ring-inset flex min-h-[56px] w-full items-start justify-between gap-4 px-4 py-4 text-left transition hover:bg-white/4"
className={[
'ops-focus-ring-inset flex min-h-[64px] w-full items-start justify-between gap-4 border-l-2 px-4 py-4 text-left transition-colors hover:border-ops-accent/30 hover:bg-white/[0.05]',
isOpen
? 'border-ops-accent/40 bg-white/[0.025]'
: 'border-transparent',
].join(' ')}
>
<span>
<span className="ops-eyebrow block text-xs font-semibold text-ops-text-muted">
Expand All @@ -56,11 +61,11 @@ export function AccordionSection({
</span>
<span
aria-hidden="true"
className={`mt-1 inline-flex h-6 w-6 items-center justify-center text-ops-accent transition-transform ${isOpen ? 'rotate-90' : ''}`}
className={`mt-1 inline-flex h-7 w-7 items-center justify-center text-ops-accent transition-transform ${isOpen ? 'rotate-90' : ''}`}
>
<svg
viewBox="0 0 12 12"
className="h-3 w-3"
className="h-3.5 w-3.5"
fill="none"
stroke="currentColor"
strokeLinecap="square"
Expand All @@ -77,7 +82,7 @@ export function AccordionSection({
role="region"
aria-labelledby={headerId}
hidden={!isOpen}
className="border-t border-ops-border-soft px-4 py-4"
className={`border-t px-4 py-4 ${isOpen ? 'border-ops-panel-border-strong' : 'border-ops-border-soft'}`}
>
{children}
</div>
Expand Down
5 changes: 4 additions & 1 deletion src/features/history/DesktopHistoryGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,10 @@ export function DesktopHistoryGrid({ model }: DesktopHistoryGridProps) {
degraded, and UN for unmarked.
</caption>
<thead role="rowgroup">
<tr role="row">
<tr
role="row"
className="shadow-[0_1px_0_var(--color-ops-border-struct)]"
>
<th
role="columnheader"
className="sticky top-0 left-0 z-30 border-r border-b border-ops-border-struct bg-ops-surface-2 px-4 py-3 text-left text-xs font-semibold tracking-[0.16em] text-ops-text-secondary uppercase"
Expand Down
Loading
Loading