/* ThreadCount — Modernist design system tokens (authored from the handoff spec) */ :root { --color-bg: #f3f2f2; --color-surface: #eae9e9; --color-text: #201e1d; --color-accent: #ec3013; --color-accent-300: #ffc4b8; /* The brand red carries white type on every primary button, accent tag and error bar, and at #ec3013 that pairing is 4.20:1 — under the 4.5:1 a label at body size needs. 600 is the same red one step down: white on it is 5.07:1, and it is 4.54:1 as text on the paper ground, so it is the shade to fill anything that has words on it. #ec3013 stays the brand mark and belongs on the rules, the progress bar and the 4px edge marks, which carry no text and need only 3:1. */ --color-accent-600: #d42a12; --color-accent-700: #b8240e; --color-divider: #cfcccb; --color-neutral-200: #e4e2e1; --color-neutral-300: #d6d3d2; /* 400 is only legible on ink — it is the muted meta colour inside the dark panels and top bars (7.8:1 there). On the paper ground it is 1.9:1, so anything that has to be read on the light side wants 600 or 700 instead. */ --color-neutral-400: #b5b1af; --color-neutral-500: #928d8a; --color-neutral-900: #2d2b2b; /* Was #7a7573, which is 4.07:1 on the paper ground and 3.75:1 on the surface tone — under the minimum for the sub-lines, table headers and tab labels this colour carries. */ --color-neutral-600: #6c6764; --color-neutral-700: #57534f; --color-neutral-800: #3a3735; --color-slip-teal: #9acbd8; /* The desktop app's chrome, and only the desktop app's. #201e1d, #f3f2f2, #ec3013 and #b8240e are the four the Android shell already ships, so nothing here is a new colour in the product; #2b2827 is one step up from the ink so the rail reads as lifted off the page header rather than welded to it, and #37332f is the lightest hairline that still shows on ink. These are literal values on purpose: the desktop scopes below remap some --color-* tokens on the ink bands, and a --tc-* token defined as var(--color-…) would be remapped along with them. */ --tc-ink: #201e1d; --tc-rail: #2b2827; --tc-hair: #37332f; --tc-on-ink: #f3f2f2; --tc-ink-idle: #d6d3d2; --tc-ink-muted: #b5b1af; --tc-on-ink-accent: #ffc4b8; --space-1: 4px; --space-2: 8px; --space-3: 12px; --space-4: 16px; --space-5: 20px; --space-6: 24px; --space-8: 32px; --font-heading: var(--font-archivo), "Archivo", system-ui, sans-serif; --font-body: var(--font-archivo), "Archivo", system-ui, sans-serif; } * { box-sizing: border-box; } html, body { margin: 0; padding: 0; background: var(--color-bg); color: var(--color-text); font-family: var(--font-body); font-size: 14px; line-height: 1.4; -webkit-font-smoothing: antialiased; } /* Chrome's text autosizing "boosts" font sizes in the Android WebView — a 15px top-bar title came out at 24px on a real phone, which is why the app's chrome looked oversized against the design. The sizes here are deliberate, so opt out of the boosting; the OS accessibility font scale still applies on top of them. */ html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; } a { color: var(--color-accent-700); } a:hover { color: var(--color-accent-600); } h1, h2, h3 { font-family: var(--font-heading); margin: 0; } b, strong { font-weight: 700; } button, input, select, textarea { font-family: var(--font-body); font-size: 14px; color: var(--color-text); border-radius: 0; } input[type="checkbox"] { accent-color: var(--color-text); } /* Buttons — flush-left labels, zero radius */ .btn { display: inline-flex; align-items: center; justify-content: flex-start; gap: 6px; min-height: 36px; padding: 6px 14px; font-weight: 600; font-size: 13px; line-height: 1.2; border: 2px solid transparent; background: transparent; color: var(--color-text); cursor: pointer; text-align: left; white-space: nowrap; text-decoration: none; } .btn:disabled { opacity: 0.45; cursor: not-allowed; } .btn-primary { background: var(--color-accent-600); border-color: var(--color-accent-600); color: #fff; } .btn-primary:not(:disabled):hover { background: var(--color-accent-700); border-color: var(--color-accent-700); } .btn-secondary { background: transparent; border-color: var(--color-text); color: var(--color-text); } .btn-secondary:not(:disabled):hover { background: var(--color-neutral-200); } .btn-ghost { background: transparent; border-color: transparent; color: var(--color-text); text-decoration: underline; text-underline-offset: 3px; text-decoration-thickness: 1.5px; } .btn-ghost:not(:disabled):hover { background: var(--color-neutral-200); } .btn-block { width: 100%; } /* Tags */ .tag { display: inline-flex; align-items: center; padding: 2px 7px; font-size: 10px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; line-height: 1.5; border: 1.5px solid transparent; white-space: nowrap; } .tag-accent { background: var(--color-accent-600); border-color: var(--color-accent-600); color: #fff; } .tag-neutral { background: var(--color-neutral-200); border-color: var(--color-neutral-200); color: var(--color-text); } .tag-outline { background: transparent; border-color: var(--color-text); color: var(--color-text); } /* Segmented control */ .seg { display: inline-flex; border: 2px solid var(--color-text); background: var(--color-bg); } .seg-opt { padding: 5px 12px; min-height: 30px; font-size: 12px; font-weight: 600; background: transparent; border: none; border-right: 2px solid var(--color-text); color: var(--color-text); cursor: pointer; white-space: nowrap; } .seg-opt:last-child { border-right: none; } .seg-opt.btn-primary { background: var(--color-accent-600); color: #fff; } /* Inputs */ .input { min-height: 36px; padding: 6px 10px; font-size: 14px; border: 2px solid var(--color-text); background: #fff; color: var(--color-text); outline: none; width: auto; max-width: 100%; } .input:focus { border-color: var(--color-accent); box-shadow: inset 0 0 0 1px var(--color-accent); } .input:disabled { background: var(--color-surface); color: var(--color-neutral-700); } select.input { appearance: none; -webkit-appearance: none; padding-right: 28px; background-image: url("data:image/svg+xml;utf8,"); background-repeat: no-repeat; background-position: right 10px center; } .field { display: flex; flex-direction: column; gap: 4px; min-width: 0; } .field label { font-size: 11px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--color-neutral-700); } .field .input { width: 100%; } /* Tables */ .table { border-collapse: collapse; width: 100%; font-size: 13px; } .table th { font-size: 11px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; color: var(--color-neutral-600); border-bottom: 2px solid var(--color-text); padding: 8px 8px 6px; } .table td { padding: 8px; border-bottom: 1px solid var(--color-divider); vertical-align: middle; } .table tr:last-child td { border-bottom: 1px solid var(--color-divider); } /* Section heads */ .sec { border-bottom: 2px solid var(--color-text); padding-bottom: var(--space-2); font-size: 12px; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; } .eyebrow { font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--color-accent-700); } .h1 { font-family: var(--font-heading); font-weight: 800; font-size: 30px; margin: var(--space-1) 0 0; letter-spacing: -0.02em; } .page-head { padding: var(--space-6) 0 var(--space-4); border-bottom: 2px solid var(--color-text); display: flex; align-items: flex-end; justify-content: space-between; gap: var(--space-4); flex-wrap: wrap; } .row-hover:hover { background: var(--color-neutral-200); } .muted { color: var(--color-neutral-700); } .num { font-family: var(--font-heading); font-weight: 800; } /* Dialogs */ .overlay { position: fixed; inset: 0; background: color-mix(in srgb, #201e1d 45%, transparent); display: flex; align-items: center; justify-content: center; z-index: 50; padding: 16px; } .dialog { background: var(--color-bg); border: 2px solid var(--color-text); padding: var(--space-6); max-height: 88vh; overflow: auto; width: 100%; } .dialog-title { font-family: var(--font-heading); font-weight: 800; font-size: 20px; } /* App shell */ /* Skip link: out of the way until it takes focus, then a solid ink chip in the top-left corner. Fixed rather than in flow so it cannot disturb the shell's flex row while it is hidden. */ .skip-link { position: fixed; top: 8px; left: -9999px; z-index: 100; background: var(--color-text); color: var(--color-bg); border: 2px solid var(--color-text); padding: 10px 14px; font-size: 13px; font-weight: 700; text-decoration: none; } .skip-link:focus { left: 8px; } /* The content landmark only holds focus so the skip link can hand it over; a ring around the whole page would be noise, and nothing else can focus it. */ main.tc-main:focus { outline: none; } /* --tc-gutter is the page's side margin. The page head bleeds out to it with a negative margin, so the two have to be one number or the ink band stops short of the edge. */ .tc-shell { display: flex; min-height: 100vh; align-items: stretch; --tc-gutter: var(--space-8); } #tc-side { --tc-rail-w: 232px; width: var(--tc-rail-w); flex: 0 0 var(--tc-rail-w); background: var(--tc-rail); color: var(--tc-ink-idle); border-right: 2px solid var(--tc-ink); display: flex; flex-direction: column; position: sticky; top: 0; align-self: flex-start; height: 100vh; /* Eleven items, a footer and a short window: the rail scrolls rather than losing Sign out off the bottom. Collapsed it must not, because the hover labels are drawn outside its width and overflow-y clips the other axis with it. */ overflow-y: auto; overflow-x: hidden; transition: width 140ms ease, flex-basis 140ms ease; } #tc-side.tc-rail-narrow { --tc-rail-w: 62px; overflow: visible; } @media (prefers-reduced-motion: reduce) { #tc-side { transition: none; } } #tc-mobilebar { display: none; } main.tc-main { flex: 1; min-width: 0; padding: 0 var(--tc-gutter) var(--space-8); } #tc-scanfab { display: none; } @media screen and (max-width: 780px) { /* main's padding drops to space-3 below; the page head bleeds by the same number or it hangs over the edge of a narrow window. */ .tc-shell { --tc-gutter: var(--space-3); } #tc-side { display: none !important; } #tc-mobilebar { display: grid !important; } #tc-scanfab { display: flex !important; } /* The fab sits above the dialog overlay; hide it while a dialog is open so it can't be tapped through (the scan dialogs have their own Camera button). */ body:has(.overlay) #tc-scanfab { display: none !important; } main.tc-main { padding: 0 var(--space-3) 110px !important; } .tc-grid { grid-template-columns: 1fr !important; } /* A grid track defaults to a min-content floor, so a wide table (the size list on a product page) stretches its column instead of scrolling inside .table-wrap and drags the whole page sideways. Let the tracks shrink and the wrapper does its job. */ .tc-grid > * { min-width: 0; } .dialog { max-width: 94vw !important; } /* Touch targets: 44px controls, 16px inputs (stops iOS zoom), 40px segmented options, scrollable tables. */ .btn { min-height: 44px; } .input, select.input { min-height: 44px; font-size: 16px; } .seg .seg-opt { min-height: 40px; } .table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; } table.table { display: block; overflow-x: auto; } /* Scoped to the desktop app's content area. As a bare `h1 !important` this reached every heading on every phone-width screen — the counter app's 15px top-bar titles came out at 24px, which is why the app's chrome looked oversized on a real device, and the marketing hero and the onboarding headings were flattened to 24px too. Every other rule in this block is scoped to the desktop shell; this one wasn't. */ main.tc-main h1, main.tc-main .h1 { font-size: 24px !important; } .page-head .btn { min-height: 44px; padding: 6px 12px; } } .table-wrap { overflow-x: auto; } @media screen and (max-width: 820px) { .tc-auth { grid-template-columns: 1fr !important; min-height: 100dvh !important; } .tc-brandpane { display: none !important; } /* Phone: brand strip on top, form starts high (keyboard-friendly), links home/demo underneath. */ .tc-authpane { align-items: flex-start !important; padding: 20px 20px calc(28px + env(safe-area-inset-bottom)) !important; } .tc-brandmobile, .tc-authfoot { display: flex !important; } .tc-auth .seg .seg-opt { min-height: 44px; } .tc-auth .btn-primary { min-height: 48px; font-size: 15px; } .mk-grid2, .mk-grid3, .mk-grid4, .mk-hero, .mk-rep { grid-template-columns: 1fr !important; } .mk-h1 { font-size: 38px !important; } .tcl-grid { grid-template-columns: 1fr !important; } .tcl-side { position: static !important; border-right: none !important; border-bottom: 2px solid var(--color-text); display: flex; flex-wrap: wrap; gap: 4px; padding: 12px 16px !important; } .tcl-side .btn { width: auto !important; } } /* The marketing nav is sticky, so an anchor jump lands the heading underneath it. */ #loop, #product, #features, #reporting, #price { scroll-margin-top: 96px; } @media screen and (max-width: 900px) { .tcm-hero, .tcm-loop, .tcm-rep, .tcm-price { grid-template-columns: 1fr !important; } .tcm-feat { grid-template-columns: 1fr 1fr !important; } .tcm-stats { grid-template-columns: 1fr 1fr !important; } .tcm-foot { grid-template-columns: 1fr 1fr !important; } .tcm-rail { display: none !important; } .tcm-navlinks { gap: 16px !important; font-size: 12.5px !important; } .tcm-hero > div:first-child { border-right: none !important; border-bottom: 2px solid var(--color-text); } #reporting > div:first-child { border-right: none !important; border-bottom: 2px solid var(--color-text); } .tcm-price > div:first-child { border-right: none !important; border-bottom: 1px solid var(--color-divider); } } @media screen and (max-width: 820px) { .tcm-foot { grid-template-columns: 1fr 1fr !important; } } @media screen and (max-width: 780px) { /* Handoff hides the desktop link row here. It leaves a second scrollable row in its place so the pages stay reachable — the handoff flags "no mobile menu was designed" as an open question. */ .tcm-navlinks, .tcm-navlogin { display: none !important; } .tcm-navmobile { display: block !important; } } @media screen and (max-width: 900px) { .tcm-headsplit, .tcm-split, .tcm-3col, .tcm-2col { grid-template-columns: 1fr !important; } /* The left cell of a .tcm-split carries no left padding of its own — on a wide screen it sits on the page gutter, which the wrap has already paid for. Collapsed to one column that zero becomes copy printed against the edge of the phone, so the gutter goes back here. */ .tcm-splitpad { padding-left: 40px; } /* A grid track floors at min-content, so a fixed-width mock inside a collapsed split stretches its column and drags the whole page sideways instead of scrolling inside its own .table-wrap. */ .tcm-split > * { min-width: 0; } .tcm-rowgrid { grid-template-columns: 1fr !important; gap: 6px !important; } .tcm-pullup { margin-top: 0 !important; } .tcm-stagger > * { padding-top: 0 !important; } } @media screen and (max-width: 560px) { .tcm-pillars { grid-template-columns: 1fr !important; } } @media screen and (max-width: 640px) { /* Phones: drop the in-page links so the sticky bar stays one row and keeps the CTAs above the fold. */ .tcm-navlinks { display: none !important; } .tcm-nav { padding: 12px 16px !important; gap: 12px !important; } #loop, #product, #features, #reporting, #price { scroll-margin-top: 72px; } /* The hero's second and third mock cards are illustration. On a phone they added ~490px of scrolling before the first real section, so show one and get on with it. */ .tcm-mock-extra { display: none !important; } .tcm-mock-first { margin-bottom: 20px !important; } } @media screen and (max-width: 560px) { .tcm-feat, .tcm-foot { grid-template-columns: 1fr !important; } .tcm-feat-lead { grid-column: auto !important; } } @media print { #tc-side, #tc-mobilebar, #tc-scanfab, .skip-link, .no-print { display: none !important; } main.tc-main { padding: 0 !important; } body { background: #fff; } } /* Update wave additions */ .btn-icon { padding: 0 8px; min-height: 26px; text-decoration: none; font-size: 16px; line-height: 1; } .kpi-strip { display: grid; grid-template-columns: repeat(4, 1fr); gap: 2px; background: var(--color-divider); border: 2px solid var(--color-text); margin-top: var(--space-4); } .kpi-strip > div { background: var(--color-bg); padding: var(--space-3); } .kpi-strip .kv { font-family: var(--font-heading); font-weight: 800; font-size: 24px; } .kpi-strip .kl { font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--color-neutral-700); font-weight: 600; margin-top: 2px; } .link-name { cursor: pointer; border-bottom: 2px solid var(--color-accent); color: inherit; text-decoration: none; } .link-name:hover { color: var(--color-accent-700); } .notice { border-left: 4px solid var(--color-accent); padding: var(--space-2) var(--space-3); font-size: 13px; font-weight: 600; margin-bottom: var(--space-3); } .bar-track { height: 10px; border: 2px solid var(--color-text); } .bar-fill { height: 100%; background: var(--color-text); } textarea.input { font-family: var(--font-body); resize: vertical; } @media screen and (max-width: 780px) { .kpi-strip { grid-template-columns: 1fr 1fr; } } /* Marketing photography: every image is greyscale, never tinted or restored on hover. */ .grayscale { filter: grayscale(1) contrast(1.08); } /* ---------- the phone app (/m): a fixed-height column, only the body scrolls */ /* The page returns a fragment, so its top bar, rule, body and nav are this column's own children. */ .tcx-app { position: fixed; inset: 0; display: flex; flex-direction: column; background: var(--color-bg); overflow: hidden; } /* Android 15 draws apps edge to edge whether they ask or not, so the ink top bar would otherwise sit under the status bar with the screen title behind the clock. The inset is padding on the bar itself, so the ink still runs to the top of the screen — the colour bleeds, the words don't. */ .tcx-topbar { padding-top: env(safe-area-inset-top, 0px); height: calc(56px + env(safe-area-inset-top, 0px)) !important; flex-basis: calc(56px + env(safe-area-inset-top, 0px)) !important; } .tcx-bar:not(:disabled):active { filter: brightness(0.86); } @keyframes tcx-sweep { 0%, 100% { top: 6px; } 50% { top: calc(100% - 9px); } } .tcx-laser { animation: tcx-sweep 1.6s ease-in-out infinite; top: 6px; } @media (prefers-reduced-motion: reduce) { .tcx-laser { animation: none; top: 50%; } } /* Bars on the counting screen carry the chart in "variance over time" */ .tcx-chart { display: flex; align-items: flex-end; gap: 5px; height: 56px; border-bottom: 1px solid var(--color-divider); } .tcx-chart > i { flex: 1; min-width: 6px; display: block; } /* Native scanning: MLKit draws the camera preview behind the WebView, so the page has to get out of the way while it runs. Only ever set inside the Android shell. */ html.tcx-native-scan, html.tcx-native-scan body, html.tcx-native-scan .tcx-app { background: transparent !important; } /* Everything the shelf screen drew goes; the scan overlay stays. `.tcx-scanui` is the class the camera overlay's own root carries (components/MScan.tsx) — if nothing on the page carries it, this hides the scan UI as well, and a coordinator mid-count is left with a bare camera picture: no LIVE/PAUSED header, no running counted/expected figure and no "Stop scanning" bar to press, only the hardware back button, which walks out of the count altogether. */ html.tcx-native-scan .tcx-app > *:not(.tcx-scanui) { visibility: hidden; } /* The overlay is drawn for the browser scanner, where the picture comes from its own . Under MLKit the picture is behind the WebView, so the overlay's ground and its camera window — the last in it, the one wrapping the video — have to let it through, or the spared UI covers the preview with flat ink and the garment can't be aimed at. The header, the last-scans panel and the bottom bar keep their own backgrounds so they stay readable over the picture. */ html.tcx-native-scan .tcx-scanui, html.tcx-native-scan .tcx-scanui .tcx-camwin { background: transparent !important; } /* The browser scanner's is still in the camera window under MLKit, with no stream to show. Android's WebView paints a source-less video as an opaque grey box with a play glyph in the middle — which is exactly what a coordinator in hands-free mode saw on a Pixel 8 Pro on 2026-09-12 instead of the shelf: the native preview was behind it the whole time. */ html.tcx-native-scan .tcx-scanui video { display: none !important; } /* On a tablet the app keeps a phone-shaped column rather than stretching a list row to 1200px — a row that wide puts the garment name and its count at opposite ends of the screen. */ @media (min-width: 620px) { .tcx-app { align-items: center; } .tcx-app > * { width: 100%; max-width: 560px; } .tcx-app { background: var(--color-surface); } } /* ---------- the desktop app (/app): dark chrome, paper content * * Everything that changes how an existing thing looks, from here down, is scoped to .tc-shell — * the wrapper components/Shell.tsx draws and nothing else in the product draws. The counter app * (/m) and the staff app (/my) build their screens out of the --color-* tokens at the top of this * file and the .tcx-* classes above, and neither ever renders a .tc-shell, so none of those * selectors can reach a phone. The unscoped rules below add new class names only — nothing the * marketing site or the two phone apps already wears. * * The chrome went dark and the content did not. A shelf count is read across a linen room under * ward lighting, and paper holds its contrast there in a way an ink panel does not. */ .tc-rail-brand { display: flex; align-items: center; gap: var(--space-2); padding: var(--space-5) var(--space-4) var(--space-3); } .tc-rail-mark { width: 14px; height: 14px; flex: 0 0 14px; background: var(--color-accent); } .tc-rail-word { font-family: var(--font-heading); font-weight: 800; font-size: 18px; letter-spacing: -0.02em; color: var(--tc-on-ink); flex: 1; min-width: 0; overflow: hidden; white-space: nowrap; } .tc-rail-facility { padding: 0 var(--space-4) var(--space-3); font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--tc-ink-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; border-bottom: 1px solid var(--tc-hair); } .tc-rail-toggle { flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; width: 30px; height: 30px; padding: 0; border: 1px solid var(--tc-hair); background: transparent; color: var(--tc-ink-idle); cursor: pointer; } .tc-rail-toggle:hover { background: var(--tc-hair); color: var(--tc-on-ink); } .tc-rail-nav { flex: 1; display: flex; flex-direction: column; } /* An item is a fixed-height icon row, so collapsed the rail is a column of even squares. */ .tc-rail-item { position: relative; display: flex; align-items: center; gap: var(--space-3); padding: 0 var(--space-4); min-height: 42px; border: none; border-bottom: 1px solid var(--tc-hair); border-left: 4px solid transparent; background: transparent; color: var(--tc-ink-idle); font-family: var(--font-body); font-size: 14px; font-weight: 500; text-align: left; text-decoration: none; cursor: pointer; width: 100%; } .tc-rail-item:hover { background: var(--tc-hair); color: var(--tc-on-ink); } .tc-rail-item:disabled { opacity: 0.45; cursor: not-allowed; } /* The accent marks the screen you are on as a rule down its edge, not as the label's colour: the vermilion is 3.5:1 against the rail, which is enough for a 4px bar and not enough for a 14px word. The word turns paper-white and heavy instead, so the item is marked twice over. */ .tc-rail-item.active { border-left-color: var(--color-accent); background: var(--tc-ink); color: var(--tc-on-ink); font-weight: 800; } .tc-rail-icon { flex: 0 0 18px; display: inline-flex; align-items: center; justify-content: center; color: var(--tc-ink-muted); } .tc-rail-item:hover .tc-rail-icon, .tc-rail-item.active .tc-rail-icon { color: var(--color-accent-300); } .tc-rail-label { min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } .tc-rail-meta { padding: var(--space-3) var(--space-4); font-size: 11px; line-height: 1.7; color: var(--tc-ink-muted); letter-spacing: 0.08em; text-transform: uppercase; } .tc-rail-foot { border-top: 1px solid var(--tc-hair); } .tc-rail-foot .tc-rail-item:last-child { border-bottom: none; } .tc-rail-role { display: inline-flex; margin-left: 6px; padding: 1px 6px; font-size: 10px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; background: var(--tc-hair); color: var(--tc-ink-idle); } .tc-rail-live { padding: 0 var(--space-4) var(--space-3); font-size: 11px; font-weight: 600; color: var(--color-accent-300); } .tc-rail-item:focus-visible, .tc-rail-toggle:focus-visible { outline: 2px solid var(--color-accent-300); outline-offset: -3px; } /* Collapsed: an icon rail. The label stays in the DOM and stays in the accessibility tree — opacity, not display or visibility — so a screen reader still reads "Delivery Rounds" off an item a sighted coordinator has to hover to name. */ /* The facility name carries the hairline under the brand when the rail is open; collapsed it is hidden, so the brand block takes the rule over or the mark runs straight into the first icon. */ .tc-rail-narrow .tc-rail-brand { justify-content: center; flex-wrap: wrap; gap: var(--space-2); padding: var(--space-4) 0 var(--space-3); border-bottom: 1px solid var(--tc-hair); } .tc-rail-narrow .tc-rail-word, .tc-rail-narrow .tc-rail-facility, .tc-rail-narrow .tc-rail-meta, .tc-rail-narrow .tc-rail-role { display: none; } .tc-rail-narrow .tc-rail-item { justify-content: center; padding: 0; min-height: 44px; border-left-width: 3px; } .tc-rail-narrow .tc-rail-label { position: absolute; left: 100%; top: 50%; transform: translateY(-50%); margin-left: 2px; padding: 7px 10px; background: var(--tc-ink); color: var(--tc-on-ink); border: 2px solid var(--tc-hair); font-weight: 600; font-size: 13px; opacity: 0; pointer-events: none; z-index: 40; transition: opacity 90ms linear; } .tc-rail-narrow .tc-rail-item:hover .tc-rail-label, .tc-rail-narrow .tc-rail-item:focus-visible .tc-rail-label { opacity: 1; } @media (prefers-reduced-motion: reduce) { .tc-rail-narrow .tc-rail-label { transition: none; } } /* The page head carries the ink the full width of the content column, so it bleeds back out * through main's gutter. * * Screens put whatever they like in this band — a counted total, a supplier filter, a "net −$412" * in accent-700 — and most of it names its colour inline as var(--color-text) or * var(--color-neutral-700), which on ink is either invisible or close to it. Rather than make * fourteen screens special-case the band, the band remaps those tokens for everything inside it: * an inline var(--color-text) resolves to paper in here and to ink everywhere else. This is the * only place in the product where a --color-* token takes a different value, it happens on a * descendant of .tc-shell, and no phone screen has one of those. */ .tc-shell .page-head, .tc-shell .tc-pagehead { --color-text: #f3f2f2; --color-bg: #201e1d; --color-surface: #37332f; --color-divider: #37332f; --color-neutral-200: #37332f; --color-neutral-300: #57534f; --color-neutral-600: #b5b1af; --color-neutral-700: #b5b1af; --color-neutral-800: #d6d3d2; --color-accent-700: #ffc4b8; background: var(--tc-ink); color: var(--tc-on-ink); margin: 0 calc(var(--tc-gutter) * -1) var(--space-6); padding: var(--space-6) var(--tc-gutter) var(--space-5); border-bottom: 4px solid var(--color-accent); display: flex; align-items: flex-end; justify-content: space-between; gap: var(--space-4); flex-wrap: wrap; } /* A field keeps its white ground, so inside one the ink has to come back or what someone types is white on white — the reporting month select and the register's search box both live up here. */ .tc-shell .page-head .input, .tc-shell .tc-pagehead .input { --color-text: #201e1d; } .tc-shell .page-head .h1, .tc-shell .tc-pagehead .h1 { color: var(--tc-on-ink); } /* The figure a screen is actually about: a count, a value, a number of gaps. */ .tc-figure { font-family: var(--font-heading); font-weight: 800; font-size: 28px; line-height: 1.05; letter-spacing: -0.02em; } .tc-meta { font-size: 11px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--color-neutral-700); } /* Tiles: the figures that sit under a page head. auto-fit rather than a fixed count, because the same strip carries three tiles on Ordering and five on Reports. */ .tc-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(168px, 1fr)); gap: 2px; background: var(--color-text); border: 2px solid var(--color-text); } .tc-tile { background: var(--color-bg); padding: var(--space-4); display: flex; flex-direction: column; gap: 2px; min-width: 0; } .tc-tile-label { font-size: 11px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--color-neutral-700); } .tc-tile-note { font-size: 12px; color: var(--color-neutral-700); } /* Panels: a bordered block with a named head. */ .tc-panel { border: 2px solid var(--color-text); background: var(--color-bg); min-width: 0; } .tc-panel-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-3); padding: var(--space-3) var(--space-4); border-bottom: 2px solid var(--color-text); font-size: 12px; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; } .tc-panel-aside { font-size: 12px; font-weight: 400; letter-spacing: 0; text-transform: none; color: var(--color-neutral-700); } .tc-panel-body { padding: var(--space-4); } .tc-panel-foot { padding: var(--space-3) var(--space-4); border-top: 1px solid var(--color-divider); } /* A list of rows fills its panel edge to edge — no body padding, the rows carry their own. */ .tc-panel-list > .tc-row:last-child { border-bottom: none; } /* Rows: one garment, one order, one request. */ .tc-row { display: flex; align-items: center; gap: var(--space-3); padding: var(--space-3) var(--space-4); border-bottom: 1px solid var(--color-divider); min-width: 0; } .tc-row-main { flex: 1; min-width: 0; } .tc-row-name { font-weight: 700; overflow: hidden; text-overflow: ellipsis; } .tc-row-meta { font-size: 12px; color: var(--color-neutral-700); } .tc-row-fig { font-family: var(--font-heading); font-weight: 800; font-size: 18px; white-space: nowrap; } a.tc-row, button.tc-row { width: 100%; background: var(--color-bg); border-left: none; border-right: none; border-top: none; color: inherit; text-decoration: none; text-align: left; font-size: 14px; cursor: pointer; } a.tc-row:hover, button.tc-row:hover { background: var(--color-neutral-200); color: inherit; } /* Anything that wants attention. * * The accent is already the brand — it is the primary button and it is the current nav item — so * a second red a metre away across the room is a guess, not a signal. A flagged tile or row is * marked three ways instead: a rule down its left edge, its figure heavier and in the darker red * that stays legible on paper, and .tc-mark next to a word saying what is wrong. The mark is * decoration and belongs behind aria-hidden; the word is the message, so screens keep the word. */ .tc-flag { border-left: 4px solid var(--color-accent); } .tc-tile.tc-flag, .tc-row.tc-flag { padding-left: calc(var(--space-4) - 4px); } .tc-flag .tc-figure, .tc-flag .tc-row-fig { font-weight: 800; color: var(--color-accent-700); } .tc-flag .tc-tile-label { color: var(--color-accent-700); } .tc-mark { display: inline-block; width: 0; height: 0; border-left: 5px solid transparent; border-right: 5px solid transparent; border-bottom: 9px solid var(--color-accent); vertical-align: -1px; margin-right: 6px; } .tag-flag { background: var(--color-accent-600); border-color: var(--color-accent-600); color: #fff; gap: 5px; } .tag-flag::before { content: ""; width: 0; height: 0; border-left: 4px solid transparent; border-right: 4px solid transparent; border-bottom: 7px solid currentColor; } /* On paper the ink band is a trap: browsers drop background colours out of a print by default, and what is left is paper-white type on white paper — a screen title that prints as nothing. So the head goes back to the light treatment for the printer, and the gutter it bleeds through goes to zero along with main's padding. */ @media print { .tc-shell { --tc-gutter: 0px; } .tc-shell .page-head, .tc-shell .tc-pagehead { --color-text: #201e1d; --color-bg: #f3f2f2; --color-surface: #eae9e9; --color-divider: #cfcccb; --color-neutral-200: #e4e2e1; --color-neutral-300: #d6d3d2; --color-neutral-600: #6c6764; --color-neutral-700: #57534f; --color-neutral-800: #3a3735; --color-accent-700: #b8240e; background: none; color: var(--color-text); margin: 0 0 var(--space-4); padding: 0 0 var(--space-2); border-bottom: 2px solid var(--color-text); } .tc-shell .page-head .h1, .tc-shell .tc-pagehead .h1 { color: var(--color-text); } }