ZDDC/shared/base.css
2026-06-11 13:32:31 -05:00

771 lines
22 KiB
CSS

/* ==========================================================================
ZDDC Shared Base — single source of truth for tokens and primitives
Included first by every tool's build.sh via ../shared/base.css
========================================================================== */
/* ── CSS custom properties ────────────────────────────────────────────────── */
:root {
/* Brand / accent (matches zddc.varasys.io website --accent) */
--primary: #2a5a8a;
--primary-hover: #1d4060;
--primary-active: #163352;
--primary-light: #e8f0f7;
/* Semantic colours */
--success: #28a745;
--warning: #d97706;
--danger: #dc3545;
--info: #17a2b8;
/* Backgrounds */
--bg: #ffffff;
--bg-secondary: #f8f9fa;
--bg-hover: #f0f4f8;
--bg-selected: var(--primary-light);
/* Text */
--text: #212529;
--text-muted: #6c757d;
--text-light: #ffffff;
/* Borders */
--border: #dee2e6;
--border-dark: #adb5bd;
/* Shape */
--radius: 4px;
/* Spacing scale — referenced by the tables tool (tables/css/table.css).
Were undefined (var() with no fallback → collapsed to 0), which left
table cells unpadded and the table flush to the viewport edges. */
--spacing-sm: 0.4rem;
--spacing-md: 0.8rem;
--spacing-lg: 1.5rem;
/* Token aliases the tables tool references under --color-*/--radius-*
names; map them to the canonical tokens (themed values flow through). */
--color-text-muted: var(--text-muted);
--color-border: var(--border);
--color-bg-elevated: var(--bg-secondary);
--radius-sm: var(--radius);
/* Typography. --font-display covers headings (Source Serif 4 — a refined
transitional serif that reads as "engineering / document / serious"
without being academic). --font is body UI text (IBM Plex Sans —
distinctive engineering sans, with proper figures and tabular nums).
Both are base64-inlined via shared/fonts.css; system fallbacks kick in
when fonts.css isn't loaded (e.g. unbuilt component preview). --font-mono
stays as a system stack; engineering tools rarely benefit from a custom
mono and platform mono fonts are already excellent. */
--font-display: 'Source Serif 4', ui-serif, Charter, 'Iowan Old Style', Georgia, serif;
--font: 'IBM Plex Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
--font-mono: 'SF Mono', 'Fira Code', 'Consolas', 'Courier New', monospace;
}
/* ── Dark mode tokens ─────────────────────────────────────────────────────── */
/* Applied via: OS preference (auto) or [data-theme="dark"] on <html> */
/* The [data-theme="light"] selector locks light mode regardless of OS pref. */
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--primary: #5fa8e0;
--primary-hover: #74b6e6;
--primary-active: #88c4ec;
--primary-light: #1a3550;
--bg: #1e1e1e;
--bg-secondary: #252526;
--bg-hover: #2d2d30;
--bg-selected: #1a3550;
--text: #d4d4d4;
--text-muted: #9d9d9d;
--text-light: #ffffff;
--border: #3e3e42;
--border-dark: #6e6e72;
}
}
/* Manual dark override — wins over media query */
[data-theme="dark"] {
--primary: #5fa8e0;
--primary-hover: #74b6e6;
--primary-active: #88c4ec;
--primary-light: #1a3550;
--bg: #1e1e1e;
--bg-secondary: #252526;
--bg-hover: #2d2d30;
--bg-selected: #1a3550;
--text: #d4d4d4;
--text-muted: #9d9d9d;
--text-light: #ffffff;
--border: #3e3e42;
--border-dark: #6e6e72;
}
/* ── Reset ────────────────────────────────────────────────────────────────── */
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
/* ── Base document ────────────────────────────────────────────────────────── */
html, body {
height: 100%;
font-family: var(--font);
font-size: 16px;
line-height: 1.5;
color: var(--text);
background-color: var(--bg-secondary);
}
/* ── Typography ───────────────────────────────────────────────────────────── */
h1, h2, h3, h4, h5, h6 {
font-family: var(--font-display);
font-weight: 600;
line-height: 1.2;
/* Source Serif 4 has subtle optical sizing; let the browser opt in
where supported (modern Chromium/Firefox). */
font-optical-sizing: auto;
}
/* Tracking numbers and other engineering identifiers should align in
columns when stacked vertically. Apply tabular figures wherever we
render structured numeric data. */
table, .tabular-nums, code {
font-variant-numeric: tabular-nums;
}
a {
color: var(--primary);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
/* ── Utility ──────────────────────────────────────────────────────────────── */
.hidden {
display: none !important;
}
.truncate {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* ── Scrollbars (webkit) ──────────────────────────────────────────────────── */
::-webkit-scrollbar {
width: 7px;
height: 7px;
}
::-webkit-scrollbar-track {
background: var(--bg-secondary);
}
::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #a0a0a0;
}
/* ── Button primitive ─────────────────────────────────────────────────────── */
.btn {
display: inline-flex;
align-items: center;
gap: 0.25rem;
padding: 0.4rem 0.85rem;
font-family: var(--font);
font-size: 0.875rem;
font-weight: 500;
line-height: 1.4;
text-align: center;
text-decoration: none;
white-space: nowrap;
vertical-align: middle;
cursor: pointer;
border: 1px solid transparent;
border-radius: var(--radius);
transition: background 0.15s, box-shadow 0.15s, border-color 0.15s, color 0.15s;
background: var(--bg-secondary);
color: var(--text);
}
.btn:disabled,
.btn[disabled] {
opacity: 0.5;
cursor: not-allowed;
}
.btn:not(:disabled):hover {
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12);
}
.btn:not(:disabled):active {
box-shadow: none;
}
/* Variants */
.btn-primary {
background: var(--primary);
color: var(--text-light);
border-color: var(--primary);
}
.btn-primary:not(:disabled):hover {
background: var(--primary-hover);
border-color: var(--primary-hover);
color: var(--text-light);
}
.btn-primary:not(:disabled):active {
background: var(--primary-active);
border-color: var(--primary-active);
}
.btn-secondary {
background: var(--bg);
color: var(--text);
border-color: var(--border);
}
.btn-secondary:not(:disabled):hover {
background: var(--bg-secondary);
}
/* Subdued / de-emphasized variant.
Used on the "Use Local Directory" button when a tool is operating
in server (online) mode — the local-dir affordance is still
available but visually quieter, since the typical user already
has the directory loaded from the server. */
.btn.btn--subtle {
background: transparent;
color: var(--text-muted);
border-color: var(--border);
box-shadow: none;
font-weight: normal;
}
.btn.btn--subtle:not(:disabled):hover {
color: var(--text);
background: var(--bg-secondary);
}
.btn-success {
background: var(--success);
color: var(--text-light);
border-color: var(--success);
}
.btn-danger {
background: var(--danger);
color: var(--text-light);
border-color: var(--danger);
}
/* Sizes */
.btn-sm {
padding: 0.25rem 0.5rem;
font-size: 0.75rem;
}
.btn-lg {
padding: 0.6rem 1.4rem;
font-size: 1rem;
}
.btn-link {
background: transparent;
border-color: transparent;
color: var(--primary);
padding-left: 0;
padding-right: 0;
}
.btn-link:not(:disabled):hover {
text-decoration: underline;
box-shadow: none;
}
/* ── App header chrome ────────────────────────────────────────────────────── */
.app-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.35rem 1rem;
background: var(--bg-secondary);
border-bottom: 1px solid var(--border);
flex-shrink: 0;
/* Let the left / right groups wrap to a second row at narrow
viewports rather than overflowing the viewport edge. row-gap
gives a small breathing strip when wrapped. */
flex-wrap: wrap;
row-gap: 0.3rem;
}
/* Left and right groups inside .app-header. Both flex-row so their
children (logo, title, action button, theme icon, etc.) lay out
horizontally rather than stacking. Left side gets a slightly
larger gap because it carries the title group and an action
button; right side is just icon buttons. */
.header-left {
display: flex;
align-items: center;
gap: 0.75rem;
/* Allow the title to shrink (and ellipsize) before the action
buttons get pushed off-screen at narrow viewports. */
min-width: 0;
flex-wrap: wrap;
row-gap: 0.3rem;
}
.header-right {
display: flex;
align-items: center;
gap: 0.5rem;
flex-shrink: 0;
}
/* Title group (title + build label). Made shrinkable so narrow
viewports don't push the action buttons out of view; the title
itself ellipsizes via the rule below. */
.header-title-group {
display: flex;
align-items: baseline;
gap: 0.5rem;
min-width: 0;
flex-shrink: 1;
}
/* Tool name inside the header. Renders in the display serif so the
tool's identity reads as a document title, not a UI label.
overflow + ellipsis on min-width:0 lets the title compress
gracefully when there's no room. */
.app-header__title {
font-family: var(--font-display);
font-size: 18px;
font-weight: 600;
color: var(--text);
letter-spacing: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
min-width: 0;
}
/* Brand logo — sits left of the title in every tool's app-header.
Self-contained: the SVG provides its own dark blue rounded background,
so no extra wrapper styling is needed. */
.app-header__logo {
width: 26px;
height: 26px;
flex-shrink: 0;
display: block;
}
/* Page-load reveal. The header is the first thing a user sees — a
short staggered fade-in over ~360ms turns "instant pop-in" into a
subtle "the tool is composing itself for you" beat. Pure CSS, no
JS; respects prefers-reduced-motion. The stagger order (logo →
title → action buttons → right-side icons) mirrors the reading
order of the chrome itself. */
@keyframes zddc-header-rise {
from { opacity: 0; transform: translateY(-4px); }
to { opacity: 1; transform: translateY(0); }
}
.app-header__logo,
.header-title-group,
.header-left > .btn,
.header-right > * {
animation: zddc-header-rise 360ms cubic-bezier(0.2, 0.7, 0.2, 1) both;
}
.app-header__logo { animation-delay: 0ms; }
.header-title-group { animation-delay: 60ms; }
.header-left > .btn { animation-delay: 120ms; }
.header-right > *:nth-child(1) { animation-delay: 180ms; }
.header-right > *:nth-child(2) { animation-delay: 220ms; }
.header-right > *:nth-child(3) { animation-delay: 260ms; }
@media (prefers-reduced-motion: reduce) {
.app-header__logo,
.header-title-group,
.header-left > .btn,
.header-right > * {
animation: none;
}
}
/* ── Build timestamp ──────────────────────────────────────────────────────── */
.build-timestamp {
font-size: 0.55rem;
color: var(--text-muted);
opacity: 0.7;
font-weight: 300;
white-space: nowrap;
padding-top: 0.15rem;
}
/* Title + timestamp stacked vertically on the left side of the header */
.header-title-group {
display: flex;
flex-direction: column;
gap: 0;
line-height: 1;
}
/* ── Icon buttons (help, theme, refresh) ─────────────────────────────────── */
/* Square, centered — overrides the asymmetric text-button padding/line-height */
#help-btn,
#theme-btn,
#refreshHeaderBtn {
width: 2rem;
height: 2rem;
padding: 0;
line-height: 1;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 1rem;
}
/* The refresh ⟳ glyph renders slightly smaller than ◐ / ? — bump to match. */
#refreshHeaderBtn {
font-size: 1.1rem;
}
/* Toast CSS lives in shared/toast.css; loaded by every tool's build. */
/* ── Empty state ──────────────────────────────────────────────────────────── */
/* The "nothing's loaded yet" screen. By default, centers its inner
content in whatever space the parent gives it (works inside a flex
column). Tools that need to overlay an existing layout (archive,
classifier) add .empty-state--overlay; the screen pins below the
app header and on top of whatever underlying layout already exists.
Inner content uses BEM-ish .empty-state__inner with two variants:
plain (left-aligned, doc-style) and --centered (centered card). */
.empty-state {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
background: var(--bg);
}
.empty-state--overlay {
position: absolute;
top: 50px; /* clear the app-header */
left: 0;
right: 0;
bottom: 0;
z-index: 10;
flex: none;
}
.empty-state__inner {
max-width: 640px;
color: var(--text-muted);
line-height: 1.5;
}
.empty-state__inner h2 {
color: var(--text);
margin: 0 0 1rem;
font-size: 1.5rem;
}
.empty-state__inner p {
margin-bottom: 1rem;
}
.empty-state__inner ul,
.empty-state__inner ol {
margin: 1rem 0;
padding-left: 1.5rem;
}
.empty-state__inner li {
margin: 0.4rem 0;
}
.empty-state__inner .note {
font-size: 0.85rem;
font-style: italic;
}
/* Centered variant: tighter max-width + centered text. Used by tools
whose empty-state reads as a "welcome card" (archive, classifier)
rather than a doc-style page (browse). */
.empty-state__inner--centered {
max-width: 500px;
text-align: center;
padding: 2rem;
}
/* Bullet list inside an empty-state — keep the bullets left-aligned
even when the surrounding card is centered. */
.welcome-list {
text-align: left;
margin: 0.5rem auto;
max-width: 400px;
}
/* ── Theme and help icon buttons ─────────────────────────────────────────── */
#theme-btn,
#help-btn {
font-size: 1rem;
}
/* ── Help panel (shared slide-out drawer) ─────────────────────────────────── */
/* Used by all four tools. Toggle open/close via shared/help.js. */
.help-panel {
position: fixed;
top: 0;
right: 0;
width: min(420px, 85vw);
height: 100vh;
z-index: 1000;
background: var(--bg);
border-left: 1px solid var(--border);
box-shadow: -2px 0 12px rgba(0, 0, 0, 0.08);
display: flex;
flex-direction: column;
transform: translateX(100%);
transition: transform 0.25s ease;
}
.help-panel:not([hidden]) {
transform: translateX(0);
}
.help-panel[hidden] {
display: flex;
transform: translateX(100%);
pointer-events: none;
}
.help-panel__header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.75rem 1rem;
border-bottom: 1px solid var(--border);
flex-shrink: 0;
background: var(--bg);
}
.help-panel__title {
font-size: 1rem;
font-weight: 700;
color: var(--text);
margin: 0;
}
.help-panel__close {
background: none;
border: none;
color: var(--text-muted);
font-size: 1.35rem;
cursor: pointer;
padding: 0.25rem 0.5rem;
border-radius: var(--radius);
line-height: 1;
transition: background 0.15s, color 0.15s;
}
.help-panel__close:hover {
color: var(--text);
background: var(--bg-secondary);
}
.help-panel__body {
flex: 1;
overflow-y: auto;
padding: 1rem 1rem 2rem;
font-size: 0.85rem;
line-height: 1.6;
color: var(--text);
}
.help-panel__body h3 {
font-size: 0.95rem;
font-weight: 700;
margin: 1.25rem 0 0.35rem;
color: var(--text);
border-bottom: 1px solid var(--border);
padding-bottom: 0.15rem;
}
.help-panel__body h3:first-child {
margin-top: 0;
}
.help-panel__body h4 {
font-size: 0.7rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.06em;
margin: 1.25rem 0 0.3rem;
padding-left: 0.5rem;
border-left: 3px solid var(--border-dark);
color: var(--text-muted);
}
.help-panel__body p {
margin: 0 0 0.5rem;
}
.help-panel__body ol,
.help-panel__body ul {
padding-left: 1.5rem;
margin: 0.3rem 0 0.5rem;
}
.help-panel__body li {
margin-bottom: 0.3rem;
}
.help-panel__body dl {
margin: 0.3rem 0;
}
.help-panel__body dt {
font-weight: 600;
color: var(--text);
}
.help-panel__body dd {
margin: 0 0 0.5rem 1rem;
color: var(--text-muted);
}
.help-panel__body code {
font-family: var(--font-mono);
font-size: 0.8em;
background: var(--bg-secondary);
padding: 0.1em 0.3em;
border-radius: 3px;
}
.help-badge {
font-size: 0.7rem;
font-weight: 600;
padding: 0.1rem 0.35rem;
border-radius: var(--radius);
vertical-align: middle;
letter-spacing: 0.02em;
}
.help-badge--draft {
color: #2563eb;
background: #eff6ff;
}
.help-badge--published {
color: #7c3aed;
background: #f5f3ff;
}
/* Shrink main content when help panel is open */
body.help-open .app-header {
margin-right: min(420px, 85vw);
}
/* ── Column filter inputs (shared across archive, classifier, transmittal) ─── */
.column-filter {
display: block;
width: 100%;
box-sizing: border-box;
margin-top: 0.25rem;
padding: 0.2rem 0.4rem;
font-size: 0.8rem;
font-family: var(--font);
border: 1px solid var(--border);
border-radius: var(--radius);
background: var(--bg);
color: var(--text);
transition: border-color 0.15s;
}
.column-filter:focus {
border-color: var(--primary);
outline: none;
box-shadow: 0 0 0 1px rgba(42, 90, 138, 0.35);
}
.column-filter::placeholder {
color: var(--text-muted);
}
/* ── Narrow-viewport behavior ─────────────────────────────────────────────────
ZDDC tools are desktop-first (engineering workstations, large monitors),
but a baseline narrow rule keeps them usable on a tablet in landscape or
a window split next to a document. Three principled moves:
1. Smaller header padding so the chrome doesn't dominate the viewport.
2. The build-timestamp inside .header-title-group is hidden — it's a
traceability artifact, never an immediate-action element. (The full
label remains visible via the help panel and the "About" surface.)
3. .header-right gap tightens; the action button next to the title
drops to a 32x32 icon-only square via the .btn-square pattern (tools
that haven't adopted .btn-square just keep the text button — graceful).
Each tool is welcome to add its own narrow-mode rules in css/layout.css;
this block is the shared baseline. */
@media (max-width: 800px) {
.app-header {
padding: 0.3rem 0.6rem;
}
.app-header__title {
font-size: 16px;
}
.header-left {
gap: 0.5rem;
}
.header-right {
gap: 0.25rem;
}
/* Hide the build-timestamp on narrow viewports — it's reference info,
not a primary affordance, and steals horizontal space from the title.
Still reachable via the help panel and DOM. */
.header-title-group .build-timestamp {
display: none;
}
/* Action buttons that have an emoji-only or symbol-only label keep
their full width; text-labeled action buttons in the header shrink
to a more compact pad to fit. */
.header-left > .btn {
padding: 0.3rem 0.6rem;
font-size: 0.85rem;
}
}
/* Very narrow (phone-width). Stack the header-left children vertically so
the title and action button each get their own line; tools can override
this in their own CSS if they have a dedicated mobile layout. */
@media (max-width: 480px) {
.app-header {
align-items: flex-start;
flex-direction: column;
gap: 0.4rem;
}
.header-left,
.header-right {
width: 100%;
justify-content: space-between;
}
}