/* === Fonts === */ @font-face { font-family: "Iosevka Etoile"; src: url("/_/fonts/IosevkaEtoile.woff2") format("woff2"); font-display: swap; } @font-face { font-family: "Iosevka Slab"; src: url("/_/fonts/IosevkaSlab.woff2") format("woff2"); font-display: swap; } /* === Theme === */ :root { --bg: #2e2e2e; --bg-panel: #434343; --bg-panel-hover: #585858; --text: #e6e6e6; --text-muted: #cfcfcf; --primary: #87458a; --primary-hover: #d64d95; --secondary: #c48401; --link: #01b6c4; --link-hover: #d6d24d; --danger: #c40141; --danger-hover: #d03467; /* Translucent scrim for badges/overlays sitting on top of media. */ --overlay: rgba(0, 0, 0, 0.55); --border: 1px solid var(--secondary); --border-dashed: 1px dashed var(--secondary); /* Subtle vertical rail for tree-nesting depth — quieter than --border so it reads as a guide, not a divider. */ --border-guide: 1px solid var(--bg-panel-hover); --space-1: 0.25rem; --space-2: 0.5rem; --space-3: 0.75rem; --space-4: 1rem; --space-5: 1.5rem; --font-xs: 0.75rem; --font-sm: 0.85rem; /* Width of the persistent left folder-tree rail (desktop). */ --tree-width: 15rem; /* Comfortable max reading width for the center column's content + footer. The shell itself is edge-to-edge; this keeps prose lines readable on wide monitors (content stays centered within the center column). */ --reading-width: 60rem; } /* Anchor / TOC jumps are handled by the .center scroll container's own scroll-padding-top (the header no longer overlaps content in the app-shell), so no html-level scroll padding is needed. */ /* === Base === */ /* App-shell: the body is a full-viewport, non-scrolling grid of two rows — the fixed-height header and a 1fr shell that owns all scrolling internally. 100dvh (not 100vh) so the mobile browser's dynamic toolbar doesn't clip the bottom row; overflow:hidden pins the header and lets each shell column scroll on its own. */ body { background: var(--bg); color: var(--text); height: 100dvh; margin: 0; padding: 0; overflow: hidden; font: 1rem "Iosevka Etoile", monospace; display: grid; grid-template-rows: auto 1fr; } * { box-sizing: border-box; margin: 0; padding: 0; } a { color: var(--text); text-decoration: none; } a:hover { color: var(--primary-hover); } .content a { color: var(--link); } .content a:hover { color: var(--link-hover); } .content a.broken { color: var(--primary-hover); text-decoration: line-through; } .content a.broken:hover { color: var(--link-hover); } hr { border: none; border-top: var(--border-dashed); margin: var(--space-4) 0; } /* === Layout primitives === .row and .col are shared flex recipes; gap-* modifiers cover the cases where the default rhythm doesn't fit. */ .row { display: flex; align-items: center; gap: var(--space-2); } .col { display: flex; flex-direction: column; gap: var(--space-3); } .gap-1 { gap: var(--space-1); } .gap-2 { gap: var(--space-2); } .gap-3 { gap: var(--space-3); } .gap-4 { gap: var(--space-4); } .space-between { justify-content: space-between; } .divider-dashed { border-bottom: var(--border-dashed); } /* === Page layout (app-shell) === The shell is a full-height flex row below the header: left rail | center | right rail. Each column owns its own vertical scroll (overflow-y:auto + min-height:0), so nothing scrolls the page as a whole. Edge-to-edge: no centered max-width container. Either rail collapses out of the flex flow when empty (:empty { display:none }), letting the center reclaim the width. */ .shell { display: flex; min-height: 0; overflow: hidden; } /* Center column: main content plus the footer beneath it, scrolling together. Content and footer are held to a comfortable reading width and centered within the (edge-to-edge) column. */ .center { flex: 1 1 auto; min-width: 0; overflow-y: auto; display: flex; flex-direction: column; align-items: center; scroll-padding-top: var(--space-4); } main { width: 100%; max-width: var(--reading-width); padding: var(--space-5) var(--space-4); flex: 1 0 auto; min-width: 0; } /* === Header / footer === Header is the app-shell's fixed top row (grid row 1 of ): full width, never scrolls. Three-column grid (breadcrumbs left, search centre, actions right) so the centre stays reserved even when search is hidden in editor mode; the far-right actions column hosts the [ACTIONS] dropdown. position + z-index keep the header (and its open dropdown) painting above the shell. Mobile (≤1100px) collapses to a compact three-column layout — see responsive block below. */ header { position: relative; z-index: 40; background: var(--bg); padding: var(--space-3) var(--space-4); border-bottom: var(--border-dashed); display: grid; grid-template-columns: 1fr minmax(0, 60rem) 1fr; grid-template-areas: "crumbs search actions"; align-items: center; gap: var(--space-2); } /* Footer rides at the end of the center column's content (scrolls with it, not pinned to the viewport). Held to the same reading width as main so its top border delimits the content, not the whole shell. */ footer { width: 100%; max-width: var(--reading-width); padding: var(--space-3) var(--space-4); border-top: var(--border-dashed); display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; } .breadcrumb { grid-area: crumbs; gap: var(--space-1); min-width: 0; } .header-actions { grid-area: actions; justify-content: flex-end; flex-wrap: wrap; } .logo { width: 1.1em; height: 1.1em; vertical-align: center; } /* === Panel === Bordered container recipe shared by listings, sidebar widgets, the tree picker, the floating TOC. .panel-floating raises the background for menus and modals that sit over content. */ .panel { border: var(--border); background: var(--bg); } .panel-floating { background: var(--bg-panel); } .panel-header { font-size: var(--font-xs); text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); border-bottom: var(--border-dashed); padding-bottom: var(--space-1); margin-bottom: 0.4rem; } .panel-body { padding: var(--space-4); display: flex; flex-direction: column; gap: var(--space-3); word-break: break-word; } .panel-footer { padding: 0.6rem var(--space-4); border-top: var(--border-dashed); display: flex; justify-content: space-between; gap: var(--space-2); } /* === Buttons === */ .btn { background: none; border: none; color: var(--text); font: inherit; cursor: pointer; padding: 0; text-decoration: none; display: inline-block; white-space: nowrap; } .btn::before { content: "["; color: var(--secondary); } .btn::after { content: "]"; color: var(--secondary); } .btn:hover { color: var(--primary-hover); } .btn-small { font-size: 0.8rem; font-weight: normal; vertical-align: middle; } .btn-tool { padding: 0 0.15rem; } .btn-block { display: flex; justify-content: space-between; align-items: baseline; width: 100%; border: none; padding: 0.3rem var(--space-3); white-space: nowrap; } .btn-fab { background: var(--bg-panel); border: var(--border); width: 3rem; height: 3rem; font-size: 1.5rem; line-height: 1; display: inline-flex; align-items: center; justify-content: center; } .btn-fab::before, .btn-fab::after { content: none; } .btn-fab:hover { background: var(--bg-panel-hover); color: var(--primary-hover); } .danger { color: var(--danger); } .danger:hover { color: var(--danger-hover); } /* Selected segmented-toggle button (view-settings modal). */ .btn.is-active { color: var(--primary-hover); } /* === Form controls === .input baseline is shared by search-input, modal inputs, and the editor textarea. Hosts layer their own background / font-family / sizing on top. */ .input { width: 100%; background: var(--bg-panel); border: var(--border); color: var(--text); font: inherit; padding: 0.3rem var(--space-2); outline: none; } .input:focus { border-color: var(--primary-hover); } /* === Typography utilities === */ .muted { color: var(--text-muted); font-size: var(--font-sm); } .small { font-size: var(--font-sm); } .caption { font-size: var(--font-xs); text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); } .truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } /* === Markdown content === */ .content { margin-bottom: 2rem; } .content h1, .content h2, .content h3, .content h4, .content h5, .content h6, main > h2 { color: var(--text); margin: 1.25rem 0 var(--space-2); line-height: 1.3; } .content h1 { font-size: 1.75rem; border-bottom: var(--border-dashed); padding-bottom: var(--space-1); } .content h2, main > h2 { font-size: 1.4rem; } .content h3 { font-size: 1.15rem; } .content p { margin: var(--space-3) 0; } .content ul, .content ol { margin: var(--space-3) 0 var(--space-3) var(--space-5); } .content li { margin: var(--space-1) 0; } .content blockquote { border-left: 3px solid var(--secondary); padding: var(--space-1) var(--space-4); color: var(--text-muted); margin: var(--space-3) 0; } .content code { font-family: "Iosevka Etoile", monospace; font-size: 0.875em; background: var(--bg-panel); padding: 0.1em 0.35em; } .content pre { background: var(--bg-panel); border: var(--border); padding: var(--space-4); overflow-x: auto; margin: var(--space-3) 0; } .content pre code { background: none; padding: 0; } .content hr { margin: var(--space-5) 0; } .content img { max-width: 100%; } .content li:has(> input.task-checkbox:checked) { color: var(--text-muted); text-decoration: line-through; } a.heading-anchor { color: var(--text-muted); margin-right: 0.4em; font-weight: normal; text-decoration: none; } a.heading-anchor:hover { color: var(--primary-hover); } /* === Data tables === Shared style for the file listing, search-suggestion dropdown, and markdown content tables. .data-table-grid adds per-cell borders + a header band for content (markdown) tables. */ .data-table { width: 100%; border-collapse: collapse; } .data-table th, .data-table td { padding: 0.4rem var(--space-3); text-align: left; } .data-table:not(.data-table-grid) tbody tr + tr { border-top: var(--border); } .data-table tr.is-active { background: var(--bg-panel-hover); } /* Hover-highlight only on devices with a real pointer. On touch, a tap or drag-scroll leaves an emulated :hover stuck on the last-touched row until the next tap — distracting while scrolling a listing. */ @media (hover: hover) { .data-table:not(.data-table-grid) tbody tr:hover { background: var(--bg-panel-hover); } } .data-table tbody tr.is-empty, .data-table tbody tr.is-empty:hover { color: var(--text-muted); background: none; cursor: default; } .data-table-grid th, .data-table-grid td { border: var(--border); } .data-table-grid th { background: var(--bg-panel); color: var(--text); } .content .data-table-grid { margin: var(--space-3) 0; font-size: 0.9rem; } /* File listing rows */ .list-item { font-size: 0.95rem; } .list-item > td { padding: 0.6rem var(--space-4); } .list-item td.icon { width: 1.25rem; text-align: center; } .list-item td.name { overflow-wrap: anywhere; } .list-item td.name a { color: inherit; display: block; } .list-item td.meta { color: var(--text-muted); font-size: 0.8rem; white-space: nowrap; text-align: right; } /* === Dropdown menu === */ .dropdown { position: relative; } .dropdown-menu { position: absolute; top: 100%; left: 0; z-index: 100; min-width: 9rem; display: none; background: var(--bg-panel); border: var(--border); } .dropdown-menu.align-right { left: auto; right: 0; } .dropdown-menu.open-up { top: auto; bottom: 100%; margin-bottom: 0.4rem; } .dropdown-menu.is-open { display: block; } .dropdown-menu.scrollable { max-height: 23rem; overflow-y: auto; } /* === Suggestion dropdown (header search + editor link picker) === Anchored to a position:relative host. Mirrors .dropdown-menu visuals with a dashed border and bg-panel-hover for the active row. */ .suggest-host { position: relative; } .suggest-dropdown { position: absolute; top: 100%; left: 0; right: 0; z-index: 200; background: var(--bg-panel); border: var(--border-dashed); border-top: none; display: none; } .suggest-dropdown.is-open { display: block; } .suggest-row { cursor: pointer; } .suggest-row > td { padding: 0.4rem 0.6rem; } .suggest-name, .suggest-path { display: block; } .suggest-name { color: var(--text); } .suggest-path { color: var(--text-muted); font-size: 0.8rem; margin-top: 0.1rem; } .suggest-footer > td { color: var(--link); font-size: var(--font-sm); } /* === Editor toolbar === Single non-wrapping row that scrolls horizontally (swipe on mobile) rather than breaking into stacked rows. A horizontal-scroll container also clips overflow-y, so open dropdown menus are pinned to the viewport via JS (editor/main.js pinMenu) to escape the clip. */ .editor-toolbar { display: flex; flex-wrap: nowrap; gap: var(--space-1); border: var(--border); border-bottom: none; padding: 0.4rem 0.6rem; background: var(--bg-panel-hover); overflow-x: auto; scrollbar-width: none; } .editor-toolbar::-webkit-scrollbar { display: none; } .editor-toolbar > * { flex-shrink: 0; } .toolbar-sep { width: 1px; background: var(--secondary); margin: 0 0.2rem; align-self: stretch; } /* === Edit form === */ .edit-form { display: flex; flex-direction: column; } /* No rails are rendered while editing, so the editor uses the full center column: drop the reading-width cap on main (and the footer) so the toolbar and CodeMirror mount span the whole width. */ body.edit-mode main, body.edit-mode footer { max-width: none; } /* CodeMirror mount. The .cm-editor visual treatment (border, bg, font, padding) lives in the CM theme (editor-build/entry.js), keyed off the same :root variables; this only sizes the container. */ .editor-cm { min-height: 60vh; } .editor-cm .cm-editor { height: 100%; } /* === Search === */ .search-form { grid-area: search; display: flex; gap: var(--space-1); position: relative; justify-self: center; width: 100%; max-width: 40rem; } .search-input { font-size: 0.9rem; } .search-card { display: flex; flex-direction: column; gap: var(--space-1); padding-bottom: var(--space-4); margin-bottom: var(--space-4); border-bottom: var(--border-dashed); word-break: break-word; } .search-card:last-child { border-bottom: none; } .search-card a { color: var(--link); font-size: 1.1rem; } .search-card a:hover { color: var(--link-hover); } /* === Floating action button === Standalone FAB buttons (the tree rail, the right rail) are mobile-only and stack bottom-right: the tree FAB anchors the bottom, the right-rail FAB sits above it. Wrapped FABs (the search actions dropdown) stay visible on desktop and, on mobile, sit in the upper slot so they never overlap the tree FAB. */ .fab { position: fixed; bottom: var(--space-4); right: var(--space-4); z-index: 50; } button.fab { display: none; } .fab-rail { bottom: calc(var(--space-4) + 3rem + var(--space-2)); } /* === Companion status === */ .companion-status { margin-left: auto; } .companion-icon { font-size: 0.9rem; line-height: 1; padding: 0.1rem 0.3rem; } .companion-on { color: var(--link); } .companion-off { color: var(--text-muted); } .companion-flyout { min-width: 14rem; padding: 0.4rem; } .companion-line { padding: var(--space-1) var(--space-2); font-size: var(--font-sm); } /* === Photo grid === */ .photo-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 0.4rem; margin-top: var(--space-3); } .photo-grid a { display: block; line-height: 0; } .photo-grid img { width: 100%; height: 140px; object-fit: cover; display: block; background: var(--bg-panel) url("/_/icons/thumb-placeholder.svg") center/2rem no-repeat; } /* === Thumbnail listing grid === File-listing variant of .photo-grid: responsive tiles that pair a thumbnail (or a file/folder icon for non-thumbnailable entries) with a truncated name label beneath. */ .thumb-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: var(--space-3); margin-top: var(--space-3); } .thumb-tile { display: flex; flex-direction: column; gap: var(--space-1); color: var(--text); border: var(--border); background: var(--bg-panel); padding: var(--space-2); } .thumb-tile:hover { background: var(--bg-panel-hover); color: var(--primary-hover); } .thumb-img { width: 100%; height: 150px; object-fit: cover; display: block; background: var(--bg) url("/_/icons/thumb-placeholder.svg") center/2rem no-repeat; } /* Wrapper so a video tile can overlay a play badge on its extracted frame. */ .thumb-media { position: relative; display: block; } .thumb-play { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); display: flex; align-items: center; justify-content: center; width: 2.5rem; height: 2.5rem; padding-left: 0.15rem; border-radius: 50%; background: var(--overlay); color: var(--text); pointer-events: none; } .thumb-icon { height: 150px; display: flex; align-items: center; justify-content: center; font-size: 3rem; color: var(--secondary); } .thumb-label { font-size: var(--font-sm); } .empty { padding: var(--space-4); text-align: center; } /* === Scrollbars === */ ::-webkit-scrollbar { width: 6px; } ::-webkit-scrollbar-track { background: #111; } ::-webkit-scrollbar-thumb { background: var(--primary); } ::-webkit-scrollbar-thumb:hover { background: var(--primary-hover); } /* === Sidebar (right rail) === Full-height column in the app-shell that scrolls its own overflow (TOC on top, page widget(s) below). Collapses out of the flex row when it has no content so the center column widens. A dashed separator to main mirrors the left tree rail; widgets within drop their panel outlines (see .panel-sidebar). */ .sidebar { width: 14rem; flex-shrink: 0; overflow-y: auto; display: flex; flex-direction: column; gap: var(--space-4); padding: var(--space-4) 0 var(--space-4) var(--space-2); border-left: var(--border-dashed); } aside.sidebar:empty { display: none; } /* Density modifier for panels in the sidebar (smaller font, tighter padding). Drops the .panel outline so the rail reads as a clean column separated from main by the .sidebar dashed border, matching the left tree rail. Inside the mobile Overlay the widgets render as plain full-width content. */ .panel-sidebar { padding: var(--space-2) var(--space-3); font-size: var(--font-sm); border: none; } /* === Table of contents === Rendered as a .panel-sidebar block in the right rail (padding + font-size come from that density class); toc.js always mounts it there, on both desktop and inside the mobile Overlay. */ .toc ul { list-style: none; margin: 0; padding: 0; } .toc li { margin: 0.15rem 0; } .toc a { color: var(--link); display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .toc a:hover { color: var(--link-hover); } .toc-h3 { padding-left: 0.8rem; } .toc-h4 { padding-left: 1.6rem; } /* === Modal === */ .modal-backdrop { position: fixed; inset: 0; background: rgba(0, 0, 0, 0.6); display: flex; align-items: flex-start; justify-content: center; z-index: 1000; padding: var(--space-4); } .modal { width: 100%; max-width: 500px; display: flex; flex-direction: column; margin-top: 6rem; position: relative; } .modal.is-dragged { margin: 0; } /* Modal headers use larger font + bright color + drag handle; everything else comes from .panel-header. */ .modal .panel-header { font-size: var(--font-sm); color: var(--text); padding: 0.6rem var(--space-4); margin-bottom: 0; cursor: move; user-select: none; } /* Modal-scoped input variant: dark bg + slab font + slightly more padding. */ .modal .input { background: var(--bg); font-family: "Iosevka Slab", monospace; padding: 0.4rem 0.6rem; } /* === Overlay (full-viewport rail host, mobile) === A distinct component from .modal: it hosts a whole rail (the folder tree or the right rail's TOC/widgets) at full screen with its own scroll and a top-right close control. overlay.js MOVES the rail node into .overlay-body, so the rules below strip the rail's column chrome (fixed width, borders, the mobile display:none) and let it render as plain full-width content. */ .overlay { position: fixed; inset: 0; z-index: 200; background: var(--bg); display: flex; flex-direction: column; overflow: hidden; } .overlay-bar { display: flex; justify-content: flex-end; padding: var(--space-2); border-bottom: var(--border-dashed); flex-shrink: 0; } .overlay-close { width: 2.5rem; height: 2.5rem; font-size: 1.5rem; } .overlay-body { flex: 1; overflow-y: auto; padding: var(--space-3) var(--space-4); -webkit-overflow-scrolling: touch; } .overlay-body .tree-sidebar, .overlay-body .sidebar { display: block; width: auto; max-height: none; border: none; padding: 0; overflow: visible; } /* Right rail keeps its column spacing (TOC above widgets) inside the overlay; the base .sidebar already sets flex-direction + gap. */ .overlay-body .sidebar { display: flex; } /* === Tree picker === */ /* Rows are click targets (navigate / toggle), not prose — suppress the text-selection highlight that double/drag clicks would otherwise leave, matching the persistent .tree-sidebar rail. */ .tree-picker { max-height: 60vh; overflow-y: auto; user-select: none; } .tree-row { display: flex; align-items: center; gap: var(--space-1); padding: 0.25rem var(--space-1); cursor: pointer; } .tree-row:hover, .tree-row.is-selected { background: var(--bg-panel-hover); } .tree-row.is-selected { border-left: 3px solid var(--primary); padding-left: calc(var(--space-2) - 3px); } .tree-row.is-disabled { color: var(--text-muted); cursor: default; } .tree-row.is-disabled:hover { background: none; } .tree-chevron { text-align: center; flex-shrink: 0; } .tree-chevron { width: 1.25rem; color: var(--secondary); } .tree-chevron.is-leaf { visibility: hidden; } .tree-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } /* Each nesting level gets a vertical guide rail down its left edge so depth stays scannable in the compact view (cf. VS Code / file-explorer trees). */ .tree-children { margin-left: var(--space-2); padding-left: var(--space-2); border-left: var(--border-guide); } .tree-selected-path { font-size: var(--font-sm); padding: var(--space-1) 0; word-break: break-all; } /* Current-page row in the navigation rail. Mirrors .tree-row.is-selected so the two tree surfaces stay visually consistent. */ .tree-row.is-active { background: var(--bg-panel-hover); border-left: 3px solid var(--primary); padding-left: calc(var(--space-2) - 3px); } .tree-row.is-active > .tree-name { color: var(--primary-hover); } /* === Tree sidebar (persistent left navigation rail) === Reuses the .tree-row / .tree-children / .tree-name / .tree-chevron modules. Desktop: a full-height column in the app-shell that scrolls its own overflow. Mobile: not laid out inline — its content is surfaced through the Overlay via .fab-tree (see responsive). */ .tree-sidebar { width: var(--tree-width); flex-shrink: 0; overflow-y: auto; padding: var(--space-4) var(--space-2) var(--space-4) var(--space-4); border-right: var(--border-dashed); font-size: var(--font-sm); /* Rows are click targets (navigate / toggle), not prose — suppress the text-selection highlight that double/drag clicks would otherwise leave. */ user-select: none; } aside.tree-sidebar:empty { display: none; } /* === Movie info box === */ .movie-info { margin: var(--space-3) 0; } .movie-info::after { content: ""; display: block; clear: both; } .movie-info .movie-poster { float: right; max-width: 200px; margin: 0 0 var(--space-3) var(--space-4); } .movie-info table { width: auto; } @media (max-width: 600px) { .movie-info .movie-poster { float: none; display: block; margin: 0 auto var(--space-3); } } /* === Diary calendar === */ /* Header row: "Chronological" on the left, the [year ▾] dropdown pushed to the right. Composes with .row (flex + centering); space-between splits the two. */ .diary-cal .panel-header { justify-content: space-between; } .diary-cal-drop .dropdown-menu { min-width: 0; } /* Per-month caption above each grid, linking to that month's anchor. */ .diary-cal-month { font-size: var(--font-sm); margin: var(--space-3) 0 0.2rem; } .diary-cal-month a { color: var(--link); } .diary-cal-month a:hover { color: var(--link-hover); } .diary-cal-grid { width: 100%; border-collapse: collapse; font-size: 0.8rem; margin-bottom: 0.4rem; } .diary-cal-grid th, .diary-cal-grid td { text-align: center; padding: 0.1rem 0.15rem; font-weight: normal; color: var(--text-muted); } .diary-cal-grid td a { color: var(--link); display: block; } .diary-cal-grid td a:hover { color: var(--link-hover); } .diary-cal-grid td.cal-empty a { color: var(--text-muted); } .diary-cal-grid td.cal-empty a:hover { color: var(--link-hover); } .diary-cal-grid td.cal-today { background: var(--bg-panel); } .diary-cal-grid td.cal-current, .diary-cal-grid td.cal-current a { color: var(--primary-hover); } .btn-block.cal-current { color: var(--primary-hover); } /* === Fitness dashboard === Server-rendered inline SVG charts. Geometry comes precomputed from Go; colors and strokes are applied here via classes so the inline SVG follows the theme palette. */ .fitness-chart { padding: var(--space-3); } .fitness-chart-header { margin-bottom: var(--space-2); } .fitness-range { width: auto; font-size: var(--font-sm); } .fitness-empty { border: var(--border-dashed); color: var(--text-muted); text-align: center; padding: var(--space-5); } .fitness-svg { display: block; width: 100%; height: auto; } .fitness-svg .chart-grid { stroke: var(--bg-panel-hover); } .fitness-svg .chart-axis { stroke: var(--text-muted); } .fitness-svg .chart-label { fill: var(--text-muted); font-size: var(--font-xs); } .fitness-svg .chart-line { fill: none; stroke: var(--link); stroke-width: 1.5; } .fitness-svg .chart-dot { fill: var(--link); } .fitness-svg .chart-goal { stroke: var(--primary-hover); stroke-dasharray: 4 3; } .fitness-svg .chart-goal-label { fill: var(--primary-hover); font-size: var(--font-xs); } /* === Responsive === */ @media (max-width: 1100px) { /* Rails are not laid out inline on mobile — their content is surfaced through the Overlay via the stacked FABs. The