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
3 changes: 2 additions & 1 deletion .prettierrc.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
"singleQuote": true
"singleQuote": true,
"endOfLine": "auto"
}
32 changes: 16 additions & 16 deletions docs/design-tokens.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ Application rules:
OpsNormal uses a small tracking scale instead of one-off component values.

- `--ops-tracking-display`: `0.10em` for the product title and large display values
- `--ops-tracking-section`: `0.04em` for legacy compact headings
- `--ops-tracking-section`: `0.08em` for compact section headings
- `--ops-tracking-eyebrow`: `0.14em` for standard eyebrows and compact labels
- `--ops-tracking-eyebrow-strong`: `0.18em` for high-level shell metadata
- `--ops-tracking-action`: `0.14em` for clipped action buttons
Expand All @@ -62,21 +62,21 @@ Application rules:
## Core surfaces

- `--color-ops-base`: `#0a0f0d`
- `--color-ops-surface-base`: `#0e1411`
- `--color-ops-surface-1`: `#121814`
- `--color-ops-surface-2`: `#1a221e`
- `--color-ops-surface-3`: `#222b27`
- `--color-ops-surface-raised`: `#1a221e`
- `--color-ops-surface-overlay`: `#222b27`
- `--color-ops-surface-interactive`: `#26342d`
- `--color-ops-surface-base`: `#101613`
- `--color-ops-surface-1`: `#151d1a`
- `--color-ops-surface-2`: `#1c2521`
- `--color-ops-surface-3`: `#24302a`
- `--color-ops-surface-raised`: `#18211d`
- `--color-ops-surface-overlay`: `#1f2a25`
- `--color-ops-surface-interactive`: `#28342e`
- `--color-ops-text-primary`: `#e6ece9`
- `--color-ops-text-secondary`: `#b4bfba`
- `--color-ops-text-muted`: `#8b9691`
- `--color-ops-border-soft`: `#ffffff12`
- `--color-ops-border-struct`: `#ffffff26`
- `--color-ops-border-strong`: `#ffffff38`
- `--color-ops-panel-border`: `#ffffff12`
- `--color-ops-panel-border-strong`: `#ffffff26`
- `--color-ops-border-soft`: `#ffffff14`
- `--color-ops-border-struct`: `#ffffff29`
- `--color-ops-border-strong`: `#ffffff3a`
- `--color-ops-panel-border`: `#ffffff1a`
- `--color-ops-panel-border-strong`: `#ffffff24`
- `--color-ops-accent`: `#6ee7b7`
- `--color-ops-accent-muted`: `#b7f7da`
- `--color-ops-accent-border`: `#6ee7b724`
Expand All @@ -92,12 +92,12 @@ Application rules:

## Hover and elevation scale

- `--ops-hover-1-bg`: `rgba(255, 255, 255, 0.01)`
- `--ops-hover-2-bg`: `rgba(255, 255, 255, 0.025)`
- `--ops-hover-1-bg`: `rgba(255, 255, 255, 0.015)`
- `--ops-hover-2-bg`: `rgba(255, 255, 255, 0.035)`
- `--ops-elevation-1`: standard card elevation
- `--ops-elevation-2`: active or hovered panel elevation
- `--ops-motion-standard`: `160ms ease-out`
- `--ops-motion-select`: `220ms cubic-bezier(0.2, 0.8, 0.2, 1)`
- `--ops-motion-select`: `cubic-bezier(0.2, 0, 0, 1)`

Application rules:

Expand Down
27 changes: 17 additions & 10 deletions public/recovery-surfaces.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,15 @@
color-scheme: dark;
--ops-fallback-panel-notch: 12px;
--ops-fallback-chip-notch: 8px;
--ops-fallback-border-soft: rgba(255, 255, 255, 0.14);
--ops-fallback-border-strong: rgba(255, 255, 255, 0.22);
--ops-fallback-base: #0a0f0d;
--ops-fallback-surface-base: #101613;
--ops-fallback-surface-raised: #18211d;
--ops-fallback-surface-overlay: #1f2a25;
--ops-fallback-text-primary: #e6ece9;
--ops-fallback-text-secondary: #b4bfba;
--ops-fallback-text-muted: #8b9691;
--ops-fallback-border-soft: rgba(255, 255, 255, 0.08);
--ops-fallback-border-strong: rgba(255, 255, 255, 0.14);
--ops-fallback-focus: rgba(183, 247, 218, 0.86);
}

Expand All @@ -12,8 +19,8 @@
min-height: 100vh;
min-height: 100dvh;
padding: 2rem;
background-color: #0a0f0d;
color: #e4e4e7;
background-color: var(--ops-fallback-base);
color: var(--ops-fallback-text-primary);
font-family:
system-ui,
-apple-system,
Expand Down Expand Up @@ -59,7 +66,7 @@
margin-top: 1rem;
font-size: 0.875rem;
line-height: 1.75;
color: #a1a1aa;
color: var(--ops-fallback-text-secondary);
}

.ops-boot-fallback-actions,
Expand Down Expand Up @@ -153,12 +160,12 @@
border: 1px solid var(--ops-fallback-border-soft);
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.035), transparent 30%),
rgba(0, 0, 0, 0.25);
var(--ops-fallback-surface-overlay);
}

.ops-crash-fallback-detail-label {
letter-spacing: 0.16em;
color: #a1a1aa;
color: var(--ops-fallback-text-muted);
}

.ops-crash-fallback-detail {
Expand All @@ -178,10 +185,10 @@
}

.ops-crash-fallback-button-muted {
color: #e4e4e7;
color: var(--ops-fallback-text-primary);
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.04), transparent 68%),
rgba(255, 255, 255, 0.05);
var(--ops-fallback-surface-raised);
border-color: var(--ops-fallback-border-soft);
}

Expand Down Expand Up @@ -227,7 +234,7 @@
gap: 0.75rem;
font-size: 0.8125rem;
line-height: 1.5;
color: #e4e4e7;
color: var(--ops-fallback-text-primary);
}

.ops-crash-fallback-checkbox {
Expand Down
88 changes: 42 additions & 46 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -225,15 +225,15 @@ function App() {
<main
id="main-content"
tabIndex={-1}
className="app-shell mx-auto flex w-full max-w-7xl flex-col gap-6 lg:gap-8"
className="app-shell mx-auto flex w-full max-w-[1280px] flex-col gap-6 2xl:max-w-[1440px] lg:gap-8"
>
<NotchedFrame
emphasis="primary"
notch="shell"
innerClassName="tactical-panel bg-[linear-gradient(180deg,rgba(110,231,183,0.10),rgba(255,255,255,0.02)),var(--color-ops-surface-1)] p-5 sm:p-6 lg:p-7"
>
<header>
<div className="grid gap-5 lg:gap-6">
<div className="grid gap-4 lg:gap-6">
<div className="flex max-w-4xl gap-4">
<div
className="hidden w-3 shrink-0 flex-col items-center self-stretch sm:flex"
Expand All @@ -255,18 +255,14 @@ function App() {
Personal Readiness Tracker
</p>
</div>
<h1 className="ops-tracking-display mt-4 text-4xl font-semibold text-ops-text-primary uppercase sm:mt-5 sm:text-5xl">
<h1 className="ops-tracking-display mt-3 text-3xl font-semibold text-ops-text-primary uppercase sm:mt-4 sm:text-4xl lg:text-5xl">
OpsNormal
</h1>
<p className="mt-4 max-w-2xl text-sm leading-7 text-ops-text-secondary sm:text-base">
<p className="mt-3 max-w-2xl text-sm leading-7 text-ops-text-secondary sm:text-base">
A local-only mirror for daily balance across work or school,
household, relationships, body, and rest. No account. No
cloud sync. No analytics layer.
</p>
<div className="mt-5 space-y-1.5" aria-hidden="true">
<span className="block h-px max-w-2xl bg-ops-border-soft" />
<span className="clip-notched ops-notch-chip block h-1 w-12 bg-ops-accent/65 shadow-[0_0_14px_rgba(110,231,183,0.18)]" />
</div>
</div>
</div>
<ErrorBoundary
Expand All @@ -288,31 +284,33 @@ function App() {
</header>
</NotchedFrame>

<InstallBanner compact={hasPriorityAlert} />
<PwaUpdateBanner
needRefresh={needRefresh}
offlineReady={offlineReady}
isApplyingUpdate={isApplyingUpdate}
updateStalled={updateStalled}
reloadRecoveryRequired={reloadRecoveryRequired}
externalUpdateInProgress={externalUpdateInProgress}
externalUpdateStalled={externalUpdateStalled}
onReload={handleApplyUpdate}
onDismiss={handleDismissBanner}
onReloadPage={handleReloadPage}
compact={hasPriorityAlert}
/>
{databaseBlockedMessage ? (
<AlertSurface
tone="attention"
title="Database Upgrade Blocked"
description={databaseBlockedMessage}
role="alert"
aria-atomic="true"
titleId="database-upgrade-blocked-title"
<div className="flex flex-col gap-3">
<InstallBanner compact={hasPriorityAlert} />
<PwaUpdateBanner
needRefresh={needRefresh}
offlineReady={offlineReady}
isApplyingUpdate={isApplyingUpdate}
updateStalled={updateStalled}
reloadRecoveryRequired={reloadRecoveryRequired}
externalUpdateInProgress={externalUpdateInProgress}
externalUpdateStalled={externalUpdateStalled}
onReload={handleApplyUpdate}
onDismiss={handleDismissBanner}
onReloadPage={handleReloadPage}
compact={hasPriorityAlert}
/>
) : null}
<BackupActionBanner prompt={backupActionPrompt} />
{databaseBlockedMessage ? (
<AlertSurface
tone="attention"
title="Database Upgrade Blocked"
description={databaseBlockedMessage}
role="alert"
aria-atomic="true"
titleId="database-upgrade-blocked-title"
/>
) : null}
<BackupActionBanner prompt={backupActionPrompt} />
</div>

<ErrorBoundary
resetKeys={[todayKey]}
Expand Down Expand Up @@ -375,21 +373,19 @@ function App() {
innerClassName="tactical-subpanel px-4 py-4 text-sm leading-7 text-ops-text-secondary"
>
<footer>
<div className="border-t border-ops-panel-border-strong pt-6">
<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 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>
</footer>
</NotchedFrame>
Expand Down
19 changes: 12 additions & 7 deletions src/components/DomainCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ interface DomainCardProps {
}

const STATUS_OPTIONS: UiStatus[] = ['nominal', 'degraded', 'unmarked'];
const STATUS_CONTROL_LABELS: Record<UiStatus, string> = {
nominal: 'NOM',
degraded: 'DEG',
unmarked: 'NONE',
};
const RADIO_CONTROL_KEYS = new Set([
' ',
'Enter',
Expand Down Expand Up @@ -53,8 +58,8 @@ export function DomainCard({
: instructionId;

const shellClassName = busy
? 'panel-shadow clip-notched ops-notch-panel-outer bg-ops-border-strong p-px'
: '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';
? 'panel-shadow clip-notched ops-notch-panel-outer h-full bg-ops-border-strong p-px'
: 'ops-domain-card panel-shadow group clip-notched ops-notch-panel-outer h-full 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 +153,7 @@ export function DomainCard({
<div className={shellClassName}>
<div
className={[
'clip-notched ops-notch-panel-inner tactical-panel flex min-h-[15rem] 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-[16rem]',
'clip-notched ops-notch-panel-inner tactical-panel ops-domain-card-shell flex h-full min-h-[13rem] 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 sm:p-5 xl:min-h-[14rem]',
spineClassName,
].join(' ')}
>
Expand All @@ -168,7 +173,7 @@ export function DomainCard({
/>
<span>{sector.shortLabel}</span>
</span>
<h3 className="mt-2 text-sm leading-5 font-semibold tracking-[0.04em] text-ops-text-primary uppercase">
<h3 className="mt-2 text-sm leading-5 font-semibold tracking-[0.04em] text-ops-text-primary uppercase md:normal-case md:tracking-[0.02em]">
{sector.label}
</h3>
<p className="mt-3 text-sm leading-6 text-ops-text-secondary">
Expand All @@ -177,7 +182,7 @@ export function DomainCard({
</div>
</div>

<div className="mt-6">
<div className="mt-5">
<div
className="ops-sector-caption border-t border-ops-border-soft pt-3"
aria-hidden="true"
Expand Down Expand Up @@ -221,14 +226,14 @@ export function DomainCard({
}}
onKeyDown={(event) => handleRadioKeyDown(event, optionIndex)}
className={[
'ops-focus-ring-chip ops-radio-chip tactical-chip-panel min-h-11 border px-2 py-2 text-center text-[11px] font-semibold tracking-[0.12em] uppercase',
'ops-focus-ring-chip ops-radio-chip tactical-chip-panel min-h-11 border px-1 py-2 text-center text-[10px] font-semibold tracking-normal uppercase',
busy ? 'cursor-wait opacity-70' : '',
isSelected
? `${content.classes} shadow-[inset_0_1px_0_rgba(255,255,255,0.08)]`
: 'ops-radio-chip-ghost text-ops-text-secondary hover:text-ops-text-primary',
].join(' ')}
>
{content.shortLabel}
{STATUS_CONTROL_LABELS[option]}
</button>
);
})}
Expand Down
2 changes: 1 addition & 1 deletion src/components/FooterProvenance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const REPO_URL = 'https://github.com/bradsaucier/opsnormal';
export function FooterProvenance() {
return (
<div
className="ops-provenance grid gap-3 sm:w-full sm:self-end lg:grid-cols-[auto_1fr_auto] lg:items-center lg:gap-8"
className="ops-provenance grid gap-3 sm:w-full sm:self-end lg:grid-cols-[auto_auto_auto] lg:items-center lg:justify-end lg:gap-8"
data-testid="footer-provenance"
>
<p className="ops-eyebrow-strong font-semibold text-ops-text-primary">
Expand Down
3 changes: 1 addition & 2 deletions src/components/HeaderTelemetry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ type TelemetrySparkState = 'nominal' | 'degraded' | 'unmarked';
const toneClassNameByTone: Record<TelemetryTone, string> = {
default: 'text-ops-text-primary',
accent: 'text-ops-accent-muted',
attention:
'bg-[var(--ops-status-degraded-bg)] text-[var(--ops-status-degraded-text)]',
attention: 'ops-telemetry-attention',
subtle: 'text-ops-text-secondary',
};

Expand Down
4 changes: 2 additions & 2 deletions src/components/SectionCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export function SectionCard({
<div
className={`clip-notched ops-notch-panel-outer ops-section-frame ops-section-emphasis-${emphasis} p-px`}
>
<section className="tactical-panel clip-notched ops-notch-panel-inner ops-section-surface p-6 sm:p-7 lg:p-8">
<section className="tactical-panel clip-notched ops-notch-panel-inner ops-section-surface p-5 sm:p-6 lg:p-8">
{emphasis === 'primary' ? (
<span className="ops-section-primary-tick" aria-hidden="true" />
) : null}
Expand All @@ -39,7 +39,7 @@ export function SectionCard({
</div>
{meta ? (
<div className="lg:flex lg:self-stretch lg:border-l lg:border-ops-border-soft lg:pl-6">
<div className="clip-notched ops-notch-chip tactical-chip-panel px-3 py-3 text-sm text-ops-text-secondary sm:min-w-[14rem] sm:text-right lg:flex lg:items-center lg:justify-end">
<div className="clip-notched ops-notch-chip tactical-chip-panel ops-section-meta-panel px-3 py-3 text-sm text-ops-text-secondary sm:min-w-[14rem] sm:text-right lg:flex lg:items-center lg:justify-end lg:p-0">
{meta}
</div>
</div>
Expand Down
Loading
Loading