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
5 changes: 5 additions & 0 deletions docs/design-tokens.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ Application rules:
- Use panel border tokens instead of one-off `border-white/10` and `bg-black/20` combinations when the surface belongs to the notch-clipped chrome language.
- Use the four-step surface ramp to create visible lift. Do not introduce intermediate dark greens for local component depth.
- Use `--color-ops-accent-deep` for structural rails, hero tail tracks, section ticks, and today rails that should recede behind live readiness status.
- Use `--ops-rail-baseline` for quiet structural emerald lines that need to align across telemetry, section ticks, history momentum, and footer separation.
- Use `--ops-section-pad` for first-level section padding so the vertical rhythm stays controlled by the app shell gap and not by ad hoc section padding.

## Hover and elevation scale

Expand All @@ -140,6 +142,7 @@ Application rules:
- Use hover 1 for quiet rows and hover 2 for interactive cards or grid cells.
- Animate transform, opacity, color, background, border, and shadow only.
- New motion must remain covered by the reduced-motion override.
- Structural baselines do not animate. They remain steady in normal, reduced-motion, and forced-colors modes.

## Shared chrome utilities

Expand Down Expand Up @@ -186,6 +189,7 @@ Application rules:
- Use tone chip panels for import, restore, recovery, and fault guidance instead of repeating inline gradient stacks.
- Use uniform calm `SignalCard` chrome for backup summary signals. Express caution with the left spine or an inline tone chip rather than recoloring the entire card amber when the state is an intended safety lock.
- Accordion status chips stay in `.ops-export-accordion-status` so badge alignment remains stable across open and collapsed recovery panels.
- Attention telemetry should stay on neutral chip chrome. Express alert state with the label pip, the left register tick, detail text, and the shared rail baseline.

Frame emphasis:

Expand All @@ -203,6 +207,7 @@ Section shell emphasis:
- `.ops-section-emphasis-standard` is the default shell weight for read-only mirrors such as history.
- `.ops-section-emphasis-support` drops the emerald outer wash and uses structural framing for support or recovery surfaces.
- Apply these through `SectionCard` only. They recalibrate shell hierarchy without changing notch geometry, focus chrome, or component signatures downstream.
- Primary emphasis uses structural lines, not glow. The section tick, hero rail, and top edge should read as one restrained instrument baseline.

Spine emphasis:

Expand Down
55 changes: 29 additions & 26 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -363,34 +363,37 @@ function App() {
</ErrorBoundary>
</div>

<NotchedFrame
emphasis="quiet"
notch="shell"
innerClassName="ops-subpanel px-4 py-4 text-sm leading-6 text-ops-text-secondary"
>
<footer>
<div className="pt-4">
<span
className="ops-rule-hairline mb-4 max-w-3xl"
aria-hidden="true"
/>
<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-strong 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="ops-footer-boundary">
<span className="ops-rule-hairline mb-4" aria-hidden="true" />
<NotchedFrame
emphasis="quiet"
notch="shell"
innerClassName="ops-subpanel px-4 py-4 text-sm leading-6 text-ops-text-secondary"
>
<footer>
<div className="pt-4">
<span
className="ops-rule-hairline mb-4 max-w-3xl"
aria-hidden="true"
/>
<div className="grid gap-8 lg:grid-cols-[minmax(0,1fr)_minmax(18rem,24rem)] lg:items-start">
<div className="lg:max-w-2xl">
<p className="ops-eyebrow-strong text-ops-text-primary">
Boundary
</p>
<p className="mt-2 text-sm leading-6 text-ops-text-secondary">
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>
</div>
</footer>
</NotchedFrame>
</footer>
</NotchedFrame>
</div>
</main>
</div>
);
Expand Down
30 changes: 18 additions & 12 deletions src/components/DomainCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { getSectorTintClassName } from '../lib/sectorVisuals';
import { getStatusContent } from '../lib/status';
import type { Sector, UiStatus } from '../types';
import { SectorGlyphMark } from './icons/SectorGlyphs';
import { StatusBadge } from './StatusBadge';

interface DomainCardProps {
sector: Sector;
Expand Down Expand Up @@ -158,11 +159,11 @@ export function DomainCard({
spineClassName,
].join(' ')}
>
<div className="flex items-start gap-3.5">
<span className="grid shrink-0 justify-items-center gap-2.5">
<div className="grid gap-4">
<div className="flex items-start justify-between gap-3">
<span
className={[
'ops-domain-glyph inline-flex h-9 w-9 items-center justify-center rounded-[2px] border border-ops-border-soft bg-ops-surface-base',
'ops-domain-glyph inline-flex h-8 w-8 items-center justify-center rounded-[2px] border border-ops-border-soft bg-ops-surface-base',
sectorTintClassName,
`ops-domain-glyph-${resolvedStatus}`,
].join(' ')}
Expand All @@ -172,22 +173,27 @@ export function DomainCard({
</span>
<span
className={[
'ops-coordinate-chip clip-notched ops-notch-chip ops-tracking-grid inline-flex min-h-6 items-center px-2 text-[10px] font-semibold uppercase',
'ops-coordinate-chip clip-notched ops-notch-chip ops-tracking-grid inline-flex min-h-7 items-center px-2.5 text-[10px] font-semibold uppercase',
sectorTintClassName,
].join(' ')}
aria-hidden="true"
>
{sectorSigil}
</span>
</span>
</div>
<div className="min-w-0">
<span className="ops-mono ops-tracking-eyebrow flex flex-wrap items-center gap-2 text-xs font-semibold text-ops-text-muted uppercase">
<span>{sector.shortLabel}</span>
</span>
<h3 className="ops-domain-title ops-tracking-caption mt-2 text-sm leading-5 font-semibold text-ops-text-primary uppercase">
{sector.label}
</h3>
<p className="ops-domain-description mt-3 text-sm leading-6 text-ops-text-secondary">
<div className="flex flex-wrap items-start justify-between gap-3">
<div className="min-w-0">
<span className="ops-mono ops-tracking-eyebrow flex flex-wrap items-center gap-2 text-xs font-semibold text-ops-text-muted uppercase">
<span>{sector.shortLabel}</span>
</span>
<h3 className="ops-domain-title ops-headline-h3 mt-1 text-ops-text-primary">
{sector.label}
</h3>
</div>
<StatusBadge status={resolvedStatus} size="sm" />
</div>
<p className="ops-domain-description mt-4 line-clamp-3 text-sm leading-6 text-ops-text-secondary">
{sector.description}
</p>
</div>
Expand Down
4 changes: 2 additions & 2 deletions src/components/FooterProvenance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ const REPO_URL = 'https://github.com/bradsaucier/opsnormal';
export function FooterProvenance() {
return (
<div
className="grid min-w-0 gap-2 sm:w-full sm:gap-3"
className="grid min-w-0 gap-2 sm:w-full sm:gap-3 lg:justify-items-end lg:text-right"
data-testid="footer-provenance"
>
<p className="ops-eyebrow-strong font-semibold text-ops-text-primary">
Provenance
</p>
<div className="flex flex-wrap items-center gap-3 sm:gap-4">
<div className="flex flex-wrap items-center gap-3 sm:gap-4 lg:justify-end">
<dl className="ops-provenance-facts">
<div>
<dt>Build</dt>
Expand Down
2 changes: 1 addition & 1 deletion src/components/SectionCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export function SectionCard({
<NotchedFrame
as="section"
outerClassName={`ops-section-frame ops-section-emphasis-${emphasis}`}
innerClassName="ops-panel ops-section-surface p-5 sm:p-6 lg:p-8"
innerClassName="ops-panel ops-section-surface p-[var(--ops-section-pad)]"
>
{emphasis === 'primary' ? (
<span className="ops-section-primary-tick" aria-hidden="true" />
Expand Down
31 changes: 30 additions & 1 deletion src/components/StatusLegend.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,35 @@
import { StatusBadge } from './StatusBadge';

export function StatusLegend() {
interface StatusLegendProps {
compact?: boolean;
}

export function StatusLegend({ compact = false }: StatusLegendProps) {
if (compact) {
return (
<div
className="ops-status-legend-compact ops-mono ops-tracking-grid flex flex-wrap items-center justify-center gap-2 text-[10px] font-semibold text-ops-text-muted uppercase"
aria-label="Legend: OK nominal, DG degraded, UN unmarked"
>
<span
className="ops-status-legend-swatch ops-status-legend-swatch-nominal"
aria-hidden="true"
/>
<span>OK</span>
<span
className="ops-status-legend-swatch ops-status-legend-swatch-degraded"
aria-hidden="true"
/>
<span>DG</span>
<span
className="ops-status-legend-swatch ops-status-legend-swatch-unmarked"
aria-hidden="true"
/>
<span>UN</span>
</div>
);
}

return (
<div className="ops-status-legend ops-mono ops-flat-panel ops-tracking-grid grid gap-2 px-3 py-2 text-[11px] text-ops-text-muted uppercase">
<p className="ops-eyebrow text-[10px] leading-none text-ops-text-muted">
Expand Down
14 changes: 10 additions & 4 deletions src/features/checkin/TodayPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ function DayCompletionRollup({
return (
<div
className={[
'ops-day-rollup ops-rollup-spine tactical-subpanel-strong mb-4 grid gap-4 p-4 sm:p-5 lg:grid-cols-[auto_minmax(0,1fr)] lg:items-center',
'ops-day-rollup ops-rollup-spine tactical-subpanel mb-4 grid gap-4 p-4 sm:p-5 lg:grid-cols-[auto_1px_minmax(0,1fr)] lg:items-center',
isComplete ? 'ops-day-rollup-complete ops-complete-badge' : '',
].join(' ')}
>
Expand All @@ -83,6 +83,10 @@ function DayCompletionRollup({
{rollupText}
</p>
</div>
<span
className="ops-rollup-divider hidden h-full min-h-16 lg:block"
aria-hidden="true"
/>
<div className="flex flex-wrap items-center gap-3 lg:justify-end">
<div
className="ops-day-rollup-meter clip-notched ops-notch-chip grid min-w-[13.5rem] grid-cols-5 gap-[2px] p-1"
Expand Down Expand Up @@ -236,13 +240,15 @@ export function TodayPanel({
) : null}

<div className="ops-direct-select-brief ops-flat-panel-strong mb-4 px-4 py-3">
{!hasEntriesForToday ? (
<p className="ops-eyebrow mb-1.5 text-[10px] text-ops-accent-muted">
Awaiting today
</p>
) : null}
<p
id={directSelectHintId}
className="text-sm leading-6 text-ops-text-secondary lg:text-base lg:leading-7"
>
{!hasEntriesForToday
? 'No sectors are marked for today; set one state below and the daily roll-up will start tracking live progress. '
: ''}
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.
Expand Down
8 changes: 3 additions & 5 deletions src/features/export/ExportBackupSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export function ExportBackupSection({
</div>

<div className="flex flex-col gap-2 sm:min-w-[13rem]">
<div className="flex flex-col gap-3 sm:flex-row lg:flex-col">
<div className="flex flex-col gap-2">
<button
type="button"
onClick={() => void onJsonExport()}
Expand All @@ -70,14 +70,12 @@ export function ExportBackupSection({
<button
type="button"
onClick={() => void onCsvExport()}
className={`${actionButtonClasses} ops-action-button-subtle`}
className="ops-export-register-link ops-focus-ring-chip"
aria-label="Export CSV"
>
Export CSV
</button>
</div>
<p className="ops-tracking-grid text-[10px] leading-4 text-ops-text-muted uppercase">
Also available: CSV
</p>
</div>
</div>

Expand Down
8 changes: 3 additions & 5 deletions src/features/export/exportPanelShared.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,13 @@ export function AccordionSection({
className="ops-export-accordion"
data-section={sectionKey}
data-open={isOpen ? 'true' : 'false'}
outerClassName={
isOpen ? 'bg-ops-panel-border-strong' : 'bg-ops-border-struct'
}
outerClassName="bg-ops-border-struct"
innerClassName={isOpen ? 'ops-subpanel-strong' : 'ops-subpanel'}
>
{isOpen ? (
<div
aria-hidden="true"
className="pointer-events-none absolute inset-x-0 top-0 h-px bg-ops-accent-border"
className="ops-export-accordion-open-tick pointer-events-none absolute inset-x-0 top-0 h-px"
/>
) : null}
<h3>
Expand All @@ -52,7 +50,7 @@ export function AccordionSection({
className={[
`ops-export-accordion-trigger ops-export-accordion-trigger-${sectionKey} ops-focus-ring-inset flex min-h-[60px] w-full items-stretch gap-3 px-4 py-4 text-left transition-[background-color,border-color,box-shadow,transform]`,
isOpen
? 'ops-export-accordion-trigger-open bg-[var(--ops-tint-1)]'
? 'ops-export-accordion-trigger-open'
: 'ops-export-accordion-trigger-collapsed',
].join(' ')}
>
Expand Down
12 changes: 6 additions & 6 deletions src/features/history/DesktopHistoryGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -175,9 +175,9 @@ export function DesktopHistoryGrid({ model }: DesktopHistoryGridProps) {
? 'ops-history-week-boundary'
: '',
isToday
? 'ops-history-today-header bg-[var(--ops-tint-1)] text-ops-accent-muted'
? 'ops-history-today-header text-ops-accent-muted'
: isSelectedColumn
? 'ops-history-selected-column-header ops-history-selection-axis-header bg-[var(--ops-tint-2)] text-ops-text-primary'
? 'ops-history-selected-column-header ops-history-selection-axis-header text-ops-text-primary'
: 'bg-ops-surface-1 text-ops-text-secondary',
].join(' ')}
scope="col"
Expand All @@ -201,7 +201,7 @@ export function DesktopHistoryGrid({ model }: DesktopHistoryGridProps) {
className={[
'ops-history-row',
isSelectedRow && hasInteractedWithHistory
? 'ops-history-row-selected bg-[var(--ops-tint-2)]'
? 'ops-history-row-selected'
: '',
].join(' ')}
>
Expand Down Expand Up @@ -277,12 +277,12 @@ export function DesktopHistoryGrid({ model }: DesktopHistoryGridProps) {
? 'ops-history-week-boundary'
: '',
isSelected && hasInteractedWithHistory
? 'ops-history-selected-cell ops-history-selection-node bg-[var(--ops-tint-3)]'
? 'ops-history-selected-cell ops-history-selection-node'
: isToday
? 'ops-history-today-cell'
: dateKey === selectedCell.dateKey &&
hasInteractedWithHistory
? 'ops-history-selected-column ops-history-selection-axis bg-[var(--ops-tint-1)]'
? 'ops-history-selected-column ops-history-selection-axis'
: isOddWeek
? 'ops-history-week-band'
: '',
Expand Down Expand Up @@ -323,7 +323,7 @@ export function DesktopHistoryGrid({ model }: DesktopHistoryGridProps) {
<NotchedFrame
withShadow={false}
outerClassName="bg-ops-border-struct"
innerClassName={`history-scroll-shell ops-subpanel-strong ops-flat-panel-strong max-h-[calc(100vh-3rem)] overflow-y-auto p-5 ${getStatusSpineClassName(detailBriefStatus)}`}
innerClassName={`history-scroll-shell ops-history-detail-shell ops-surface-overlay-card ops-flat-panel-strong max-h-[calc(100vh-3rem)] overflow-y-auto p-5 ${getStatusSpineClassName(detailBriefStatus)}`}
>
<HistoryDetailBrief
dateKeys={dateKeys}
Expand Down
Loading
Loading