/* Shared visual language. Page styles consume semantic roles, never palette values. */
:root {
    /* The palette primitives are declared by the palette layer, public/css/palettes/*.css,
       which head.html loads before this file. Nothing here may name a hex that belongs to a
       palette: a role points at a weight, and swapping the palette repoints all of them. */

    /* Semantic roles */
    --color-surface-page: var(--palette-neutral-0);
    --color-surface-raised: var(--palette-neutral-0);
    --color-surface-muted: var(--palette-primary-10);
    /* A disabled control is neutral, not muted: surface-muted is a blue tint and reusing it
       here would read as an active selection rather than an unavailable field. */
    --color-surface-disabled: var(--palette-neutral-10);
    --color-surface-brand: var(--palette-primary-60);
    --color-surface-brand-hover: var(--palette-primary-70);
    --color-surface-brand-subtle: var(--palette-primary-5);
    --color-surface-selected: var(--palette-primary-15);
    --color-surface-overlay: var(--palette-neutral-100);
    /* A scrim is the translucent wash behind a close disc or over a colored surface on hover.
       surface-overlay is the opaque full-screen backdrop, so it cannot stand in for one. */
    --color-surface-scrim: rgba(50, 50, 67, 0.3);
    --color-surface-scrim-hover: rgba(50, 50, 67, 0.45);
    --color-surface-inverse: var(--palette-neutral-0);
    --color-surface-danger: var(--palette-negative-60);
    --color-surface-success: var(--palette-positive-60);
    /* Subtle tints are the pale wash behind an inline message, as opposed to the strong fill
       of a badge or a button: the same meaning at a tenth of the weight. */
    --color-surface-danger-subtle: var(--palette-negative-10);
    --color-surface-success-subtle: var(--palette-positive-10);
    --color-surface-warning-subtle: var(--palette-warning-10);
    /* The informational wash. Information is the secondary brand family (Nimblr's teal; the
       same ramp as primary on a one-color palette), not the navy chrome. */
    --color-surface-info-subtle: var(--palette-secondary-10);
    /* Hover steps for the strong fills. One step per meaning, not one per Bulma variant. */
    --color-surface-danger-hover: var(--palette-negative-70);
    --color-surface-success-hover: var(--palette-positive-70);
    /* The open half hour in the service-hour grid, which is a filled tile in a grid of them rather
       than a success message. Split off the success surface, which also fills the preference
       switches, so a brand can answer for one without answering for the other. */
    /* The closed half hour, split off the muted surface for the same reason, so a brand can
       answer for the empty tiles without answering for every muted panel. */
    --color-surface-halfhour-empty: var(--color-surface-muted);
    --color-surface-halfhour: var(--color-surface-success);
    --color-surface-halfhour-hover: var(--color-surface-success-hover);
    /* A calendar day with no open time in it says the same thing as a closed half hour, so it
       reads the same role and one brand line moves both. Named for the day rather than the half
       hour because the availability calendar paints whole days, not tiles of thirty minutes. */
    --color-surface-day-closed: var(--color-surface-halfhour-empty);
    /* The drawer the service-hour grid is drawn on. Transparent by default, because the grid has
       always sat straight on the page, and split out as a role so a brand can put a ground under
       the tiles without every other panel block taking one too. */
    --color-surface-grid: transparent;
    --color-surface-warning-hover: var(--palette-warning-70);
    /* The contrast surface is the near-black chrome fill Bulma calls is-dark/is-black. It has
       to flip with the theme, which is why a literal white or a text role cannot stand in:
       both leave white text on a white fill in one of the two themes. */
    --color-surface-contrast: var(--palette-neutral-100);
    --color-surface-contrast-hover: var(--palette-neutral-90);
    /* One hover and one pressed step for every control. Bulma darkens each variant by its own
       amount, which is exactly the per-component drift this is meant to collapse. */
    --color-surface-hover: var(--palette-neutral-5);
    --color-surface-pressed: var(--palette-neutral-10);
    /* The drawer a table opens under an entry, as opposed to another entry: the schedule grid
       under an appointment template today. It is a resting fill and not a hover step, so the row
       reads as attached to the one above it whether or not the cursor is on it.

       Split off the hover surface, which is the gray that row was only showing under the cursor,
       so the default keeps the value the page already used and a brand can move a drawer without
       moving every hover in the portal. EverHealth takes it a step darker, because its open tiles
       are white and this gray is two points off white. The default cannot follow it there: its
       muted surface and its pressed step are the same weight, so a darker drawer would swallow
       the tiles sitting on it. */
    --color-surface-detail-row: var(--color-surface-hover);
    /* A well is the inset container behind copyable code or a snippet, not a raised card. */
    --color-surface-well: rgba(50, 50, 67, 0.08);
    --color-surface-hero: var(--palette-primary-60);
    --color-surface-drop-target: color-mix(in srgb, var(--palette-primary-60) 63%, transparent);
    /* The element being dragged, as opposed to the place it may land: the brand fill with just
       enough transparency to show the list moving underneath it. Written as #123456f2 in four
       rules across two sheets, which is the legacy brand blue at 95%. */
    --color-surface-brand-translucent: color-mix(in srgb, var(--palette-primary-60) 95%, transparent);
    /* The attention fill: the pale yellow band behind a legal warning, the rule under a wizard
       title, the calendar button and the row that flashes after it is saved. It is not the
       warning status, which reports a problem, so it takes the amber family at a tint weight
       rather than the warning roles. Four sheets each drew it as its own #FFDD7D. */
    --color-surface-attention: var(--palette-badge-15);
    --color-border-attention: var(--palette-warning-40);
    /* Dark navy on an attention (pale yellow) fill — calendar button, wizard rule, etc. */
    --color-text-on-attention: var(--palette-primary-60);
    /* A secondary action that has to read as distinct from the navy chrome, and the decorative
       accent on the SMS landing page. Both were teal, which is the secondary brand family. */
    --color-surface-accent: var(--palette-secondary-60);
    --color-surface-accent-hover: var(--palette-secondary-70);
    /* Preference-panel compose chrome: notification chips and dropdown triggers shared one
       Material-ish blue (hsl(219, 64%, 54%)). Distinct from teal info and orange actions. */
    --color-surface-compose: var(--palette-compose-60);
    --color-surface-compose-hover: var(--palette-compose-60);
    /* The variable tags themselves: the chips toRender() mints inside a contenteditable message
       body for ${contact.firstName} and its siblings. They shared the compose surface with the
       left-column dropdown triggers that insert them, which is one name for a chip sitting on a
       white message and a button sitting on the page. A brand that wants the chip to read as its
       own mark could not say so without moving the trigger to match. Wired to the surface it used
       to read, so a brand with no opinion renders what it rendered before. */
    --color-surface-message-tag: var(--color-surface-compose);
    --color-border-compose: var(--palette-compose-70);
    /* "Web scheduling only" label on the self-scheduling page. */
    --color-text-web-scheduling: var(--palette-indigo-60);
    /* Patient SMS landing / 404 CTA cyan (#009EA5), not the secondary teal. */
    --color-surface-cyan: var(--palette-cta-60);
    /* Google-blue CTAs scoped to the patient SMS landing page. */
    --color-action-link: var(--palette-link-60);
    --color-action-link-hover: var(--palette-link-70);
    --color-action-link-disabled: var(--palette-link-40);
    /* vue-multiselect's own greens (#41B883 / #369A6E). Not the portal success green and not
       the orange action: the CDN widget painted chips with these before the token bridge. */
    --color-surface-multiselect: var(--palette-chip-60);
    /* The label on that chip and on a highlighted option, split off --color-text-inverse so a brand
       that moves the chip surface to a pale step can answer for the text with it. */
    --color-text-multiselect: var(--color-text-inverse);
    --color-surface-multiselect-hover: var(--palette-chip-70);
    --color-text-multiselect-icon: var(--palette-chip-80);
    --color-surface-multiselect-remove: var(--palette-chip-90);
    /* The label on that remove state, split off --color-text-danger so a brand that moves the
       surface underneath it can answer for the text in the same pass. The generic danger text is a
       shared role on eleven rules in vendor-manual.css, so re-pointing it for one widget state is
       not available. Wired to what those rules already named, so a brand with no opinion renders
       what it rendered before. */
    --color-text-multiselect-remove: var(--color-text-danger);
    /* The "select all" group header when it is highlighted. The widget paints that row with the
       body text colour and the overlay named it that way, which put a text role behind a fill and
       left the row outside the palette: a brand could move every other option hover and this one
       stayed neutral. Wired to what the rule already drew, so nothing renders differently until a
       brand aims it. */
    --color-surface-multiselect-group-highlight: var(--color-text-primary);
    --color-text-multiselect-group-highlight: var(--color-text-inverse);
    /* Carousel dots on the SMS landing page: #BBB idle, #717171 active. */
    --color-surface-dot: var(--palette-neutral-40);
    --color-surface-dot-active: var(--palette-neutral-75);
    /* WhatsApp's own green. A vendor identity rather than a theme color, so it belongs with the
       provider chips. badge-5 rather than the lighter badge-4: this fills a large white-labeled
       CTA, and #59CE72 carried white text at 2.00:1, which fails even the large-text bar.
       badge-5 measures 3.56:1, so the button now passes at the size it is used. */
    --color-surface-provider-whatsapp: var(--palette-badge-5);
    /* The scrim over an uploaded image while its delete control is showing. This one must stay
       near-black in both themes: the thing underneath it is a photograph, not a page surface,
       so a role that flips would leave white text on a white mask. */
    --color-surface-media-mask: #111111;
    /* A provider chip identifies a vendor, so it needs a hue that carries no status meaning.
       That is what the badge colors are for. */
    --color-surface-provider-acuity: var(--palette-badge-6);
    --color-surface-provider-drchrono: var(--palette-badge-9);
    --color-surface-provider-outlook: var(--palette-badge-1);
    --color-surface-provider-carecloud: var(--palette-badge-5);
    /* The message preview and the conversation view, which are one widget drawn twice: a phone's
       messaging app, with the thread on a panel, a received bubble on the left, a sent bubble on
       the right, a voice bubble among them and a timestamp under each.

       Every surface here reads the --palette-chat-* family, which each palette declares with the
       same six values because they are not brand: they are the handset the portal is drawing a
       picture of, and a patient's phone does not wear the customer's colours. That family holds
       exactly what ui.css painted by hand before these roles existed, so the widget renders what it
       has always rendered under any palette that does not say otherwise.

       They read that family rather than the brand ramps because a bubble is only a bubble while it
       differs from the panel behind it, and no role can promise that across palettes: the panel was
       --color-surface-muted (P10) and the received bubble --palette-neutral-10, two slots a palette
       fills independently, and in Nimblr's both hold #F5F5F5. The bubble was painted the exact
       colour of the panel it sits on, and the widget lost its shape for every EHR that is not
       DrChrono, which is the only one whose brand aims these roles somewhere else. Contrast between
       two surfaces is not something the palette layer can be asked to keep, so it is not asked: a
       brand that wants the widget on its own colours moves these roles in its override sheet and
       answers for both sides of the pair there, the way palette-overrides/everhealth.css does, and
       test/contrast.test.js holds every configuration to a floor either way. */
    --color-surface-chat-panel: var(--palette-chat-5);
    --color-border-chat: var(--palette-chat-20);
    --color-text-chat-timestamp: var(--palette-chat-60);
    --color-surface-chat-received: var(--palette-chat-15);
    --color-surface-chat-sent: var(--palette-chat-50);
    --color-surface-chat-voice: var(--palette-chat-40);
    /* The label on a sent bubble and the timestamp under it, split off the shared text roles so a
       brand can darken the bubble and answer for what sits on it in the same pass. Both are wired
       to what those bubbles already inherited, so a brand with no opinion renders unchanged. */
    --color-text-chat-sent: var(--color-text-primary);
    --color-text-chat-sent-timestamp: var(--color-text-chat-timestamp);
    /* The dashboard banner, which is chrome on some brands and page on others. views/banner.html
       used to paint straight from --color-surface-info and --color-text-inverse-muted, so a brand
       that wanted the banner to read as page rather than as chrome could only get there by
       out-specifying that rule from a sheet in the head, and banner.html carries its own style
       block in the body. That is what the !important in both places was for. Wired to the roles it
       used to read, so a brand with no opinion renders what it rendered before, and a brand with
       one moves these two and touches no selector. */
    --color-surface-banner: var(--color-surface-info);
    --color-text-banner: var(--color-text-inverse-muted);
    --color-surface-environment-development: var(--palette-badge-7);
    --color-surface-environment-staging: var(--palette-badge-10);
    --color-surface-environment-prerelease: var(--palette-badge-4);
    --color-text-brand: var(--palette-primary-60);
    /* A configuration value that is set here rather than inherited, and the "(Advanced)" and
       "(Default)" markers beside it. This is the brand accent: Nimblr's orange, which a one-color
       palette answers for with the same ramp as its primary. */
    --color-text-highlight: var(--palette-accent-60);
    /* The preference-panel toggle icons, which used to read --color-text-highlight directly. That
       role has two jobs: the parenthesised (Default) and (Advanced) markers, and these icons. A
       brand that wants the marker to keep answering in orange while its icons follow the brand had
       no way to say so while both read one name. Wired to the role it used to read, so a brand with
       no opinion renders what it rendered before. */
    --color-text-panel-icon: var(--color-text-highlight);
    /* The scope pickers: the selects that say which location, appointment type or calendar the
       texts below them belong to. They were painted with an inline style that named
       --color-text-highlight, so they answered as a marker even though they are a control the user
       operates and not a parenthesised note. Same split as --color-text-panel-icon above, and for
       the same reason: a brand may want its markers orange and its controls on the brand. Wired to
       the role the inline styles used to name, so a brand with no opinion renders what it did. */
    --color-text-select-marker: var(--color-text-highlight);
    --color-text-primary: var(--palette-neutral-100);
    --color-text-secondary: var(--palette-neutral-80);
    --color-text-muted: var(--palette-neutral-60);
    --color-text-inverse: var(--palette-neutral-0);
    --color-text-inverse-muted: rgba(255, 255, 255, 0.7);
    --color-text-placeholder: var(--palette-neutral-60);
    --color-text-disabled: var(--palette-neutral-50);
    --color-text-danger: var(--color-text-inverse);
    /* text-danger is the white label on a strong danger surface; this is the opposite case,
       dark red body text on a pale danger tint. */
    --color-text-danger-strong: var(--palette-negative-60);
    /* Status text on a subtle wash, as opposed to the -60 fills, which are meant to carry white
       text. A label on a tint needs the darker weight to stay readable. */
    --color-text-success-strong: var(--palette-positive-70);
    /* Amber is the lightest of the three at the same weight, so warning text steps one
       further to clear AA on its own tint: measured 5.32:1 at -80 against 4.34:1 at -70. */
    --color-text-warning-strong: var(--palette-warning-80);
    --color-text-info-strong: var(--palette-secondary-70);
    /* The label drawn on --color-surface-contrast. text-inverse cannot serve here: it is white
       in both themes, and the contrast surface is near-white in the dark one. */
    --color-text-contrast: var(--palette-neutral-0);
    --color-border-default: var(--palette-neutral-20);
    --color-border-strong: var(--palette-neutral-30);
    /* A disabled field's border matches its own surface, so the control reads as one flat
       inert block rather than an editable box. */
    --color-border-disabled: var(--palette-neutral-10);
    --color-border-inverse: var(--palette-neutral-0);
    --color-border-inverse-muted: rgba(255, 255, 255, 0.2);
    --color-border-brand: var(--palette-primary-60);
    /* The border a focused field or select wears. Solid, and the same brand base the vendor
       bundle drew it in: this role used to be that base at 25% alpha, which is a border nobody
       can see on a white form, and it was written out as a hex, so it kept the blue after the
       palette went red. The translucent version of the same idea is --color-shadow-focus below,
       which is where the glow around a focused control belongs. */
    --color-border-brand-focus: var(--palette-primary-60);
    --color-border-danger: var(--palette-negative-60);
    --color-border-warning: var(--palette-warning-60);
    /* Information is the secondary brand family (teal on Nimblr; primary's twin on EverHealth). */
    --color-border-info: var(--palette-secondary-60);
    --color-border-contrast: var(--palette-neutral-100);
    --color-border-status-positive: var(--palette-positive-60);
    /* A caret or chevron is drawn with border-color but reads as an icon, so it follows the
       action color rather than the structural border neutrals. */
    --color-border-caret: var(--color-action-primary);
    --color-border-caret-hover: var(--palette-neutral-100);
    /* Both shadow roles are the same ink at two strengths. The low one was transparent enough that
       the inset on a field and the lift under a dropdown panel disappeared against a white page,
       which is what the raised surfaces are drawn on almost everywhere, so it carries more of the
       ink now. Still well clear of --color-shadow-high, which stays the strength for a modal or a
       floating button rather than for an edge. */
    --color-shadow-low: rgba(50, 50, 67, 0.18);
    --color-shadow-high: rgba(50, 50, 67, 0.3);
    /* Pre-audit nav drop shadow used Bulma's near-black, not the EverHealth neutral. */
    --color-shadow-navbar: rgba(10, 10, 10, 0.2);
    /* One focus ring for every control. Bulma tints the ring per button variant, which is
       what unification is meant to remove: a focus ring means focus, not which button.
       Translucent because it is a glow over whatever the control sits on, and derived with
       color-mix because that is the only way to put an alpha on a custom property. color-mix is
       not understood by the mail clients the inliner in server/routes.js targets, so it stays
       confined to roles no email ever paints: a focus glow and a drag-and-drop target. */
    --color-shadow-focus: color-mix(in srgb, var(--palette-primary-60) 25%, transparent);

    /* The primary action is the brand accent (Nimblr's orange; primary's twin on a one-color
       palette). The navy chrome stays on primary so a button and a navigation bar are not the
       same color. */
    --color-action-primary: var(--palette-accent-60);
    --color-action-primary-hover: var(--palette-accent-70);
    /* The Add control that closes an editable table, split off the primary action so a brand can
       answer for it separately. It is not the page's call to action: a page has one of those and it
       is usually Save, whereas Add appears once per table and there can be four on a screen.

       Three roles rather than one, because a brand may want to invert the control rather than only
       recolour it: the fill, the frame and the label move independently. All three are wired to the
       primary action here, which is the filled orange the portal has always drawn, so a brand with
       no opinion renders exactly as before. Border and fill resolving to the same colour is what
       makes it read as a fill; a brand that parts them gets an outline. */
    --color-surface-add: var(--color-action-primary);
    --color-surface-add-hover: var(--color-action-primary-hover);
    --color-border-add: var(--color-action-primary);
    --color-text-add: var(--color-text-inverse);
    /* A marker that has to be seen on top of a brand-colored surface, which is what the navigation
       bar is. The accent is the color the portal actually painted that "you are here" dot in, and
       on a one-color palette it is still a different weight than the bar's fill. */
    --color-surface-on-brand: var(--palette-accent-60);
    /* A chart series carries no meaning beyond "not the previous series", so the series run
       starts at the brand blue and then spends the badge colors rather than the status families,
       whose hues would read as good and bad slices. */
    --color-chart-series-1: var(--palette-primary-60);
    --color-chart-series-2: var(--palette-badge-2);
    --color-chart-series-3: var(--palette-badge-6);
    --color-chart-series-4: var(--palette-badge-11);
    --color-chart-series-5: var(--palette-badge-12);
    --color-chart-series-6: var(--palette-badge-14);
    --color-chart-series-7: var(--palette-badge-15);
    --color-chart-series-8: var(--palette-badge-9);
    --color-chart-series-9: var(--palette-badge-8);
    --color-chart-series-10: var(--palette-badge-16);
    --color-chart-empty: var(--color-border-strong);
    --color-status-negative: var(--palette-negative-60);
    --color-status-positive: var(--palette-positive-60);
    --color-status-info: var(--palette-secondary-60);
    --color-status-medium: var(--palette-warning-60);
    --color-surface-info: var(--palette-secondary-60);
    --color-text-info: var(--color-text-inverse);
    /* Panel section headings. Split out of --color-text-info, which the three owning sheets read
       directly, so a brand can repaint the heading band's label without moving the colour every
       other informational surface draws its text in. */
    --color-text-panel-heading: var(--color-text-info);
    --color-surface-warning: var(--palette-warning-60);
    /* Bulma $warning is pale yellow (#FFDD57). Labels on it were rgba(0,0,0,0.7), never white:
       white-on-yellow is what made preference warnings illegible. Appointment status chips
       (pending, unreachable, …) keep their own --color-appointment-*-text roles. */
    --color-text-warning: rgba(0, 0, 0, 0.7);

    /* Domain status: source: public/css/ui.css:320. */
    --color-appointment-confirmed: #00C853;
    --color-appointment-confirmed-text: white;
    /* Domain status: source: public/css/ui.css:321. */
    --color-appointment-booked: #2979FF;
    --color-appointment-booked-text: white;
    /* Domain status: source: public/css/ui.css:322. */
    --color-appointment-bumped: #B388FF;
    --color-appointment-bumped-text: black;
    /* Domain status: source: public/css/ui.css:323. */
    --color-appointment-declined: #FF1744;
    --color-appointment-declined-text: white;
    /* Domain status: source: public/css/ui.css:324. */
    --color-appointment-feedback: #FFEA00;
    --color-appointment-feedback-text: black;
    /* Domain status: source: public/css/ui.css:325. */
    --color-appointment-none: #D500F9;
    --color-appointment-none-text: white;
    /* Domain status: source: public/css/ui.css:326. */
    --color-appointment-noshowed: #AA00FF;
    --color-appointment-noshowed-text: white;
    /* Domain status: source: public/css/ui.css:327. */
    --color-appointment-no-slot: #00B8D4;
    --color-appointment-no-slot-text: white;
    /* Domain status: source: public/css/ui.css:328. */
    --color-appointment-pending: #FF9100;
    --color-appointment-pending-text: white;
    /* Domain status: source: public/css/ui.css:329. */
    --color-appointment-prospect: #FF3D00;
    --color-appointment-prospect-text: white;
    /* Domain status: source: public/css/ui.css:330. */
    --color-appointment-recovery-reschedule-workflow: #FF80AB;
    --color-appointment-recovery-reschedule-workflow-text: black;
    /* Domain status: source: public/css/ui.css:331. */
    --color-appointment-reminder: #00BFA5;
    --color-appointment-reminder-text: white;
    /* Domain status: source: public/css/ui.css:332. */
    --color-appointment-rescheduled: #651FFF;
    --color-appointment-rescheduled-text: white;
    /* Domain status: source: public/css/ui.css:333. */
    --color-appointment-unreachable: #263238;
    --color-appointment-unreachable-text: white;
    /* Domain status: source: public/css/ui.css:334. */
    --color-appointment-unsuccessful: #607D8B;
    --color-appointment-unsuccessful-text: white;
    /* Domain status: source: public/css/ui.css:335. */
    --color-appointment-empty: #CFD8DC;
    --color-appointment-empty-text: black;

    /* Mail. The notification mails are the one surface outside the portal that this file dresses,
       and they are listed apart because their constraints are not the portal's: the sheet is
       inlined by juice before it is sent, an email client resolves nothing at read time, and a
       reader cannot follow a hover or a focus. Roles the portal already names are reused rather
       than restated here; what follows is only what the mail surface asks for and the portal has
       no equivalent of.
       Two values were snapped onto primitives already in the ramps rather than given near
       duplicates of their own: the page ground was #EAEAEA (dE 1.4 from neutral-15) and the muted
       text was #777777 (dE 1.2 from neutral-75). Both sit under the just-noticeable difference,
       so the ramps stay honest and no reader can see the move. The page and the subtle fill
       therefore resolve to one value today; they are two roles because they answer to two
       different things and either may move alone. */
    --color-surface-mail-page: var(--palette-neutral-15);
    --color-surface-mail-card: var(--palette-chat-5);
    --color-surface-mail-subtle: var(--palette-neutral-15);
    --color-surface-mail-contrast: var(--palette-neutral-110);
    --color-text-mail-on-contrast: var(--palette-neutral-15);
    --color-border-mail-subtle: var(--palette-chat-15);
    --color-text-mail-muted: var(--palette-neutral-75);
    /* The mail heading and its primary button, split into the two roles the portal splits brand
       into. Its own family rather than a step on the secondary ramp: under Nimblr it carries the
       same lightness as secondary-60 at a higher chroma. */
    --color-text-mail-accent: var(--palette-teal-60);
    --color-surface-mail-accent: var(--palette-teal-60);
    --color-text-mail-highlight: var(--palette-secondary-60);
    --color-surface-mail-warning: var(--palette-warning-75);
    /* The transcript a report can embed. The chat primitives are the same in every palette on
       purpose: a quoted conversation is a record of what was said, not brand chrome. */
    --color-surface-mail-chat-received: var(--palette-chat-15);
    --color-surface-mail-chat-sent: var(--palette-chat-50);
    --color-text-mail-chat: var(--palette-chat-90);
    --color-text-mail-chat-timestamp: var(--palette-chat-60);

    /* Type faces. Which face the portal is set in is a property of the brand, so the value comes
       from the brand manifest through the --brand-font-* properties views/head.html publishes,
       the same way the logos do. The fallbacks are the Nimblr stack, which is what this file used
       to declare outright: a page rendered without a manifest keeps the face it always had.
       Headings are a separate role because the design system sets them in a second family, which
       is the one shape a single --font-family-base could not express. */
    --font-family-base: var(--brand-font-base, "Niveau Grotesk", sans-serif);
    --font-family-heading: var(--brand-font-heading, var(--font-family-base));
    /* Mono is not brand-owned: no design system in play publishes a monospace face, and the
       generic keyword lets each platform pick the one it has. */
    --font-family-mono: monospace;
    /* No icon faces here on purpose. A Font Awesome family is not typography: it is how a content
       code selects a glyph, so a rule naming one is drawing an icon rather than setting text. The
       four page stylesheets that name one keep it, because hoisting it here put an icon face on
       pages that never loaded those sheets. See the note in type-theme.css. */

    /* Type scale. Steps are named by weight like the palette, not by the component that happens
       to use them, so a control can change step without renaming anything. 300 is the body size
       every page inherits. 100 to 700 are the sizes the design system publishes, which the
       portal's four most-used sizes already sit on; 800 and up exist because the portal has
       display text that the design system's page does not cover. */
    --text-size-100: 0.75rem;
    --text-size-200: 0.875rem;
    --text-size-300: 1rem;
    --text-size-400: 1.25rem;
    --text-size-500: 1.5rem;
    --text-size-600: 1.75rem;
    --text-size-700: 2rem;
    --text-size-800: 2.5rem;
    --text-size-900: 3rem;
    --text-size-1000: 4rem;

    /* Weights. Bulma and the page sheets between them use the keywords and the numbers
       interchangeably, and a keyword cannot be compared to a number, so every weight is a
       number here.
       The installed Niveau subset carries 200, 300, 400, 500, 700 and 900. prominent is the one
       step with no face behind it: a rule asking for 600 is matched up to the 700 file, so it
       renders identically to strong today. The step is kept because the design system does
       distinguish the two, and because naming it is what makes the gap visible.
       450 appears in three authored rules. It is deliberately given no step: it matches the 500
       face, so it is medium spelled wrong rather than a weight of its own. */
    --font-weight-light: 300;
    --font-weight-regular: 400;
    --font-weight-medium: 500;
    --font-weight-prominent: 600;
    --font-weight-strong: 700;
    --font-weight-black: 900;

    /* Line heights. Unitless, so they multiply whatever step the element carries. base is the
       ratio Bulma sets on body and is therefore what almost everything inherits today. */
    --line-height-tight: 1.125;
    --line-height-snug: 1.25;
    --line-height-base: 1.5;

    /* Retained aliases for legacy stylesheets. */
    --dark-blue: var(--palette-primary-80);
    --orange: var(--color-action-primary);
    --teal: var(--color-status-info);
    /* --white is gone rather than repointed. Its two call sites were a label on the brand
       banner and the login card's own surface, which are two different roles, so an alias
       for the value could only have been right for one of them. */
}
