/* ========================================================================== 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 */ /* 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; } }