/* ============================================================================
   Orris Bench: design tokens.

   THIS FILE IS THE SINGLE SOURCE OF TRUTH FOR THE SITE. Changing a color here
   changes it everywhere. Nothing else on this site may hold a literal color,
   size, radius or duration: not a stylesheet, not a template, not an inline
   style, not a block editor setting. If a value is needed that is not here,
   the answer is to decide what role it plays and add the role, not to write a
   hex somewhere else.

   IT MUST STAY IN STEP WITH THE APPLICATION. Every color below is copied
   verbatim from:

       desktop/src/Orris.App/Themes/Orris.axaml     the palette
       desktop/src/Orris.App/App.axaml              the shipped type scale
       desktop/src/Orris.App/TextScale.cs           the ratios that derive it
       docs/tools/brandmark.py                      the mark

   The role names deliberately mirror the application's resource keys, so that
   Orris.axaml and this file can be read side by side: OrrisAccent is
   --ob-accent, OrrisSurfaceRaised is --ob-surface-raised, OrrisTextMuted is
   --ob-text-muted. That is not decoration. A website whose amber has drifted
   half a step from the application's amber looks broken on the one screen
   where a perfumer sees both at once, and the only defense against that drift
   is being able to diff two files by eye.

   The reasoning behind every value is in docs/brand/identity.md. Where a
   number here has a non-obvious reason, the reason is in the comment rather
   than in the other document, because a token nobody understands is a token
   somebody overrides.

   WHY THERE IS NO THEME SWITCH. App.axaml sets RequestedThemeVariant="Default",
   which means the application follows the operating system and offers the
   perfumer no toggle of its own. The site does the same, through
   prefers-color-scheme, and for the same reason: the reader has already stated
   this preference once, at the level where it belongs. If a manual switch is
   ever added it is added to both, or the two stop agreeing.
   ============================================================================ */

:root {

  /* Tells the browser both variants are supported, so form controls,
     scrollbars and the caret follow the palette instead of arriving in the
     platform's default light chrome on a near black page. */
  color-scheme: light dark;

  /* ==========================================================================
     1. SURFACES

     The page is warm paper rather than white. From Orris.axaml: "White at a
     bench in daylight glares, and a parchment ground is what lets an amber
     accent read as amber instead of as orange." The ground and the accent were
     chosen together; neither survives being swapped for a neutral.
     ========================================================================== */

  /* The page. Never use on a card that sits on the page: the page and the
     panel have to differ or the panel is doing nothing. */
  --ob-surface: #F4EFE4;

  /* Anything lifted off the page: a card, a panel, a field, a popup, a menu.
     Never the page ground, and never a full bleed section. */
  --ob-surface-raised: #FFFCF6;

  /* Anything recessed: a table header, a well, a footer. Never behind accent
     colored text; that pair is 4.41:1 and misses AA. */
  --ob-surface-sunken: #E9E1D1;

  /* The two neutral washes every interactive surface uses. Naming them once is
     what stops each component inventing its own idea of what hovering looks
     like, which is the whole reason these exist in the application. */
  --ob-hover: #EAE1CE;
  --ob-pressed: #DED2BA;

  /* A selected row or tab. Always carries weight or a border as well: color
     alone is never a state, per UX-Principles.md. */
  --ob-selected: #F0DFC2;

  /* A disabled control's fill. Never for a read only value the reader still
     has to read; that is body text on a raised surface. */
  --ob-disabled: #EDE7DA;

  /* ==========================================================================
     2. INK
     ========================================================================== */

  /* Body text, headings, values, figures. A near black warm brown, not black:
     pure #000 on parchment is the one combination that makes a warm ground
     look dirty. Never set below 0.75 opacity. The .caption role in the
     application was 0.65 and was raised to 0.75 for exactly this reason, since
     "the odor description under a material's name is a caption" and captions
     here carry real content. */
  --ob-text: #241B12;

  /* Field labels, captions, table headers, the sentence under a number.
     Never a value, a total, or anything the reader will act on. */
  --ob-text-muted: #6A5B49;

  /* Text inside a disabled control, and nothing else. This is 2.45:1 against
     --ob-disabled and therefore below AA on purpose: WCAG 1.4.3 exempts
     disabled controls, and the principles require that a disabled control
     explains itself in a tooltip. The low contrast says "not now"; the tooltip
     says why. A disabled control without that tooltip is a bug, not a style. */
  --ob-disabled-text: #9E9382;

  /* ==========================================================================
     3. THE ACCENT

     One accent, a burnt resin amber, close to the color of orris butter
     itself. There is no secondary brand color and adding one is not a decision
     available to this site.

     The hex satisfies two constraints at once, which is why it is this dark:
     cream sits on it at 5.47:1 so it can fill a button, and it reads as text
     on parchment at 4.99:1. See docs/brand/identity.md section 4 for why this
     is amber and not the violet an iris would suggest.
     ========================================================================== */

  --ob-accent: #96570F;

  /* Hover on an accent surface, and hover borders.

     READ THIS BEFORE USING IT. --ob-on-accent on --ob-accent-hover is 4.04:1,
     which fails AA for normal text. The application has the same gap in
     AccentButtonBackgroundPointerOver. On this site an accent button's hover
     state DARKENS to --ob-accent-pressed instead. This token is for borders,
     icon strokes and hover fills that carry no text. */
  --ob-accent-hover: #B06B1B;

  /* The pressed accent, and also the accent tone for any text that needs more
     contrast than --ob-accent gives: on --ob-accent-soft it is 6.09:1, on
     --ob-surface-sunken 6.02:1, and cream on it is 7.48:1. Three of the four
     contrast near misses in this palette are fixed by reaching for this
     instead of --ob-accent. */
  --ob-accent-pressed: #7A4508;

  /* A tint: pill and badge backgrounds, icon wells, the selected accent wash.
     Text on it is --ob-accent-pressed, never --ob-accent (4.45:1). */
  --ob-accent-soft: #F1E1C6;

  /* Text and glyphs sitting on an accent fill. Exists only for that. Never on
     the page, never on a card. */
  --ob-on-accent: #FFF9F0;

  /* ==========================================================================
     4. STATUS

     A perfumer reads these next to numbers that matter, so they are saturated
     enough to be told apart at a glance and dark enough to be read as text.

     Every one of them arrives with an icon and a word as well. Color is never
     the only signal, and here that rule has teeth: --ob-caution #8A5A0B and
     --ob-accent #96570F differ by a hair, so amber alone cannot distinguish
     "this is the primary action" from "this needs attention". Caution always
     appears in its soft backed badge; the accent never appears as a filled
     badge.
     ========================================================================== */

  /* Pass, verified, in stock, complete. Never for "cannot verify". */
  --ob-good: #3D6B36;
  --ob-good-soft: #E1EDDA;

  /* Cannot verify, running low, draft, due soon, superseded. Amber is never a
     pass. The compliance verdict has three values and this is the third one;
     rendering it as green would be worse than having no verdict at all. */
  --ob-caution: #8A5A0B;
  --ob-caution-soft: #F7E8C6;

  /* Fail, over an IFRA limit, past a date, blocked, out of stock. Not for a
     warning that is only advice, and not on a destructive control that is not
     the primary path: destructive actions stay quiet until hovered unless they
     are the confirmation's own primary button. */
  --ob-critical: #9E2E22;
  --ob-critical-soft: #F7DED8;

  /* ==========================================================================
     5. BORDERS

     Three roles, and the third one is the one most likely to be got wrong.
     ========================================================================== */

  /* The hairline between rows, around a card, under a header. Decoration, and
     exempt from the 3:1 rule at 1.38:1 because nothing depends on seeing it. */
  --ob-border: #D8CCB6;

  /* A divider that has to be seen: under a table header, between two panels.
     Still only 1.98:1, so still not enough to identify a control. */
  --ob-border-strong: #BCAA8C;

  /* A FORM CONTROL'S OWN OUTLINE. WCAG 1.4.11 wants 3:1 for the boundary that
     identifies a control, and neither border above reaches it. This is
     TextControlPlaceholderForeground from the application, which is the same
     hex in both theme dictionaries and the only tone in the palette that
     clears 3:1 against every surface in both variants: 3.53:1 on the page and
     3.96:1 on a card in light, 4.60:1 and 4.24:1 in dark. Being identical in
     both variants is why it has no dark override below. Never used as text. */
  --ob-border-field: #8B7C68;

  --ob-border-width: 1px;

  /* A focused field grows its border on the bottom edge only. Keeping the
     thickness change to one edge is what stops the text inside shifting by a
     pixel the moment the field is clicked, which is the reason
     TextControlBorderThemeThicknessFocused is 1,1,1,2 in the application. */
  --ob-border-width-focus: 1px 1px 2px;

  /* ==========================================================================
     6. FOCUS

     Two rings, dark on light, so the ring survives being drawn over either a
     parchment page or an amber button. The accent ring is 4.99:1 against the
     page, well clear of the 3:1 a focus indicator needs.
     ========================================================================== */

  --ob-focus-ring: #96570F;
  --ob-focus-ring-offset-color: #FFFCF6;
  --ob-focus-ring-width: 2px;
  --ob-focus-ring-offset: 2px;

  /* ==========================================================================
     7. CATEGORICAL SERIES, FOR CHARTS

     THESE DO NOT CHANGE BETWEEN LIGHT AND DARK, and that is the point. From
     Orris.axaml: "Heart is the same rose on the formula screen, on the
     dashboard and on a sheet printed for the bench; a reader who has learned
     that once should never have to learn it again." Every one is mid tone so
     it holds against parchment and against a near black ground without being
     swapped. They are deliberately absent from the dark block at the bottom of
     this file; putting them there would be a bug.

     They match ChartPalette in desktop/src/Orris.App/Controls/ChartModels.cs.
     The two must change together or a legend will disagree with its own chart.
     ========================================================================== */

  --ob-series-1: #4EA6C4;   /* cool blue, the volatile lift; top notes */
  --ob-series-2: #C75B7A;   /* rose; heart */
  --ob-series-3: #8A6A3F;   /* amber wood; base */
  --ob-series-4: #6E8F4E;   /* moss */
  --ob-series-5: #7B6BB0;   /* violet */
  --ob-series-6: #D08A32;   /* ochre */

  /* Present but not a note: solvent, and material nobody has placed. A diluent
     drawn in a note color reads as an ingredient with a smell, which it is
     not. */
  --ob-series-muted: #8C8C8C;

  /* Series 1 is 2.42:1 and series 6 is 2.49:1 against the parchment page,
     under the 3:1 a graphical object needs. Both are comfortable in dark mode.
     Rather than shift two colors the application has already committed to,
     every filled series mark carries this hairline so its edge is perceivable
     whatever its fill does. Charts are also labeled directly rather than by
     legend, and never encode meaning in color alone. */
  --ob-series-stroke: #8B7C68;
  --ob-series-stroke-width: 1px;

  /* ==========================================================================
     8. TYPOGRAPHY

     Inter, bundled with the application through the Avalonia.Fonts.Inter
     package under the SIL Open Font License 1.1. Self hosted here, with
     OFL.txt beside the font files, and not renamed. Self hosting is not a
     preference: a font fetched from a third party is a request the reader did
     not ask for, on a site whose whole argument is that it does nothing behind
     your back, and it is a flash of unstyled text on the one element that must
     not move.
     ========================================================================== */

  --ob-font-sans: Inter, "Segoe UI", system-ui, -apple-system, sans-serif;

  /* Figures read in columns, and every identifier: lot codes, CAS numbers,
     batch numbers. From TextScale.cs: "Numbers are read in columns and
     compared down the page, so the figures must line up. A proportional face
     here makes 0.008 and 0.080 look the same length." A figure sitting inline
     in a sentence stays in Inter with font-variant-numeric: tabular-nums, so
     it does not change face mid sentence. */
  --ob-font-mono: Consolas, Menlo, monospace;

  /* --------------------------------------------------------------------------
     THE ONE NUMBER.

     Every size below is this times a fixed ratio. That is not a stylistic
     choice, it is TextScale.Apply's contract, and the reason is written in
     TextScale.cs: "The ratios are fixed rather than each size being settable:
     a caption that has drifted to the same size as a heading is how a screen
     stops having a shape, and nobody wants to tune seven numbers to read a
     list of materials."

     1rem rather than the application's 14, because the medium is different and
     the medium's own default is the reader's stated preference. A site that
     sets a pixel body size overrides a setting the reader changed on purpose.
     The ratios are the identity; the base belongs to the medium.

     To make a section larger, raise this inside that section. A hero does
     that, and gets a heading larger than the application's largest step
     without the site growing an eighth ratio that nothing else uses.
     -------------------------------------------------------------------------- */
  --ob-text-scale: 1rem;

  /* The six steps, with the ratios from TextScale.Apply. At the application's
     body size of 14 these come out as 10.5, 11.5, 14, 15, 18 and 24, which is
     exactly the scale in App.axaml. */
  --ob-text-small:    calc(var(--ob-text-scale) * 0.75);
  --ob-text-caption:  calc(var(--ob-text-scale) * 0.82);
  --ob-text-body:     var(--ob-text-scale);
  --ob-text-subhead:  calc(var(--ob-text-scale) * 1.07);
  --ob-text-title:    calc(var(--ob-text-scale) * 1.29);
  --ob-text-heading:  calc(var(--ob-text-scale) * 1.71);

  /* --------------------------------------------------------------------------
     LINE HEIGHTS, AS UNITLESS RATIOS.

     Each one is a ratio of ITS OWN font size, never of the body size. Getting
     that wrong has a recorded cost in this codebase: there was no
     HeadingLineHeight, so a page heading borrowed TitleLineHeight, which is
     1.35 times the TITLE size and only 1.02 times the heading size. The line
     box came out shorter than the glyphs and, in the comment's words, "the
     bottom of every p, g and y in a page title was cut off."

     CSS's unitless line-height is defined as exactly this arithmetic, which is
     the one place where the web idiom and the C# are the same thing. Never
     write a line height in px or rem here.

     1.3 is the floor for a heading because Inter's ascent and descent together
     come to about 1.21. Anything tighter clips descenders.
     -------------------------------------------------------------------------- */
  --ob-leading-small:   1.4;
  --ob-leading-caption: 1.4;
  --ob-leading-body:    1.45;
  --ob-leading-subhead: 1.4;
  --ob-leading-title:   1.35;
  --ob-leading-heading: 1.3;

  /* --------------------------------------------------------------------------
     PRINT SIZES, THE ONE PLACE A PHYSICAL UNIT IS THE RIGHT ANSWER.

     Everything above is relative, because a reader's browser text setting is a
     statement about a screen at arm's length and the site has no business
     overriding it. Paper is not that. A printed sheet has a physical size, the
     reader's setting says nothing about it, and the figure that matters is how
     small the type is in millimeters when somebody with a skin condition holds
     the page up to check an allergen against a note from a dermatologist.

     These are here rather than in print.css because print.css was the one file
     in the theme carrying literal font sizes, and one of them contradicted the
     rule the same file states twice: it declares an 11 pt floor and "10.5 pt is
     the smallest the house prints", then set 9 pt on the URL printed after an
     external link. The URL is the smallest type on the sheet, on the document
     whose whole purpose is legibility. Nothing checks print.css against these
     tokens, so the only defense is that the values live in one place.

     11 pt is the application's own printed weigh sheet rule. 10.5 pt is the
     floor, and it is the small step of the screen scale at the application's
     base size of 14, which is why it is the floor and not a rounder number.
     -------------------------------------------------------------------------- */
  --ob-print-body: 11pt;
  --ob-print-small: 10.5pt;

  /* Weight does the work size does not. There is no Light and no Black.
     SemiBold is a subhead, a card heading, a table header, a selected row.
     Bold is a metric and a page title. Nothing else. */
  --ob-weight-regular: 400;
  --ob-weight-semibold: 600;
  --ob-weight-bold: 700;

  /* Body and help text wraps between 50 and 80 characters. The application
     computes pixel widths from the body size to achieve this, because Avalonia
     has no ch unit; CSS has one and can say it directly. --ob-measure is the
     default for any column of prose. */
  --ob-measure-narrow: 50ch;
  --ob-measure: 68ch;
  --ob-measure-wide: 80ch;

  /* The widest the page content ever gets. Wide enough for a table of figures
     with a material name beside its grams, narrow enough that a paragraph
     never runs the full width; prose inside it is still capped at
     --ob-measure. */
  --ob-page-max: 76rem;

  /* ==========================================================================
     9. SPACING

     One 4 px grid, from UX-Principles.md: "All spacing comes from one grid.
     Padding and gaps that come from 'whatever looked right' are how an
     interface gets ragged." Tight is 1 to 2, comfortable is 3 to 4,
     sectioning is 5 to 6.

     In rem rather than px so that spacing grows with the reader's text size.
     A layout whose type scales and whose gaps do not closes up at large text,
     which is the same failure the application's fixed pixel card widths had
     before TextScale took them over.
     ========================================================================== */

  --ob-space-1: 0.25rem;   /*  4 px */
  --ob-space-2: 0.5rem;    /*  8 px */
  --ob-space-3: 0.75rem;   /* 12 px */
  --ob-space-4: 1rem;      /* 16 px */
  --ob-space-5: 1.5rem;    /* 24 px */
  --ob-space-6: 2rem;      /* 32 px */
  --ob-space-7: 3rem;      /* 48 px */

  /* Related controls sit at 1 to 2. Unrelated groups are separated by 4 to 5
     OR a divider, never both. */
  --ob-gap-tight: var(--ob-space-2);
  --ob-gap: var(--ob-space-3);
  --ob-gap-section: var(--ob-space-5);

  /* ==========================================================================
     10. RADII

     Fluent ships 4 and the application overrides it to 6, because 4 is "a
     shade mean for a screen read at arm's length". Nothing on this site is
     rounder than 10; see docs/brand/identity.md section 8 on why a 24 px
     radius belongs to a different brand.
     ========================================================================== */

  --ob-radius-control: 6px;    /* button, input, select, list row, tab */
  --ob-radius-overlay: 8px;    /* popover, menu, dialog */
  --ob-radius-panel: 8px;      /* a bounded region of a page */
  --ob-radius-card: 10px;      /* a card, which is a panel with its own heading */
  --ob-radius-pill: 10px;      /* a count beside a heading */
  --ob-radius-icon-well: 9px;  /* the tinted disc a card's glyph sits in */

  /* The mark's tile, as a proportion rather than a length, because
     brandmark.py draws it at 0.18 of the tile width at every size. A CSS
     rendering of the mark uses this; a placed PNG or SVG does not need it. */
  --ob-radius-mark: 18%;

  /* ==========================================================================
     11. CONTROL METRICS

     Taken from the application's own overrides of Fluent, so a form on the
     site and a form in the application are the same size and shape. Avalonia
     Thickness is written left,top,right,bottom or horizontal,vertical; the CSS
     order below is the translation, not a second decision.
     ========================================================================== */

  --ob-control-min-height: 34px;      /* TextControlThemeMinHeight, ComboBoxMinHeight */
  --ob-button-padding: 7px 14px;      /* ButtonPadding 14,7 */
  --ob-field-padding: 6px 8px 7px 12px; /* TextControlThemePadding 12,6,8,7 */
  --ob-row-padding: 7px 12px;         /* ListBoxItemPadding 12,7 */
  --ob-menu-item-padding: 7px 12px;   /* MenuFlyoutItemThemePadding 12,7 */
  --ob-cell-padding: 4px 10px;        /* DataGridCell 10,4 */
  --ob-table-header-padding: 8px 12px;
  --ob-card-padding: var(--ob-space-4);
  --ob-pill-padding: 1px 8px;

  --ob-icon-well-size: 34px;
  --ob-icon-size: 18px;
  --ob-icon-stroke-width: 1.6px;

  /* Minimum 32 by 32 for a pointer target, 44 by 44 for anything used on a
     tablet at the bench. A weigh sheet and a shop checkout are both in the
     second category. */
  --ob-hit-min: 32px;
  --ob-touch-min: 44px;

  /* The only shadow on the site. From Border.card: BoxShadow 0 1 2 0 #14000000,
     where 0x14 is alpha 20 of 255. A card is bounded by its border and its
     surface; the shadow is a whisper, not the boundary. */
  --ob-shadow-card: 0 1px 2px 0 rgba(0, 0, 0, 0.08);

  /* ==========================================================================
     12. MOTION

     Motion is feedback, not decoration: every animation answers "what just
     changed?", "where did this come from?", or "where can I get back to?" If
     it answers none of them it is cut. Durations are the standard set from
     UX-Principles.md; the application's clickable card uses 0.12s for its
     border, which is the feedback step below.
     ========================================================================== */

  --ob-motion-feedback: 120ms;   /* hover, press, focus */
  --ob-motion-panel: 220ms;      /* a panel or a content swap */
  --ob-motion-page: 320ms;       /* a whole page transition, and nothing else */

  /* Movement out is faster; movement in settles slower. */
  --ob-ease-out: cubic-bezier(0.2, 0, 0, 1);
  --ob-ease-in: cubic-bezier(0.4, 0, 1, 1);
}

/* ============================================================================
   DARK

   The application HAS a dark palette: Orris.axaml carries a full Dark theme
   dictionary with its own value for every Orris role, and App.axaml sets
   RequestedThemeVariant="Default" so the application follows the system. So
   this block exists, and its values are that dictionary.

   It is the same palette by lamplight rather than a different one. The ground
   goes to a warm near black rather than a blue gray, so amber still reads as
   resin. Every pair in here clears AA comfortably; the four near misses
   recorded in docs/brand/identity.md are all light mode only.

   What is deliberately NOT overridden here:
     - the categorical series, which are variant independent by design
     - --ob-border-field, which is the same hex in both theme dictionaries
     - type, spacing, radii, metrics and motion, none of which depend on light
   ============================================================================ */

@media (prefers-color-scheme: dark) {
  :root {

    /* Surfaces */
    --ob-surface: #16120E;
    --ob-surface-raised: #211A14;
    --ob-surface-sunken: #0F0C09;
    --ob-hover: #2C2419;
    --ob-pressed: #382E20;
    --ob-selected: #3D2F1A;
    --ob-disabled: #1E1913;

    /* Ink */
    --ob-text: #F2E9DA;
    --ob-text-muted: #B4A48F;
    --ob-disabled-text: #6E6357;

    /* The accent. Lighter here, because on a near black ground the burnt amber
       of the light palette disappears. Cream is no longer the text that sits on
       it: --ob-on-accent inverts to a near black, at 8.28:1. */
    --ob-accent: #E0A44C;
    --ob-accent-hover: #F0B865;
    --ob-accent-pressed: #C08A35;
    --ob-accent-soft: #3A2A12;
    --ob-on-accent: #1E1405;

    /* Status */
    --ob-good: #8CC077;
    --ob-good-soft: #1E2C18;
    --ob-caution: #E6BA5C;
    --ob-caution-soft: #33270E;
    --ob-critical: #EC8375;
    --ob-critical-soft: #3A1E19;

    /* Borders */
    --ob-border: #3A3026;
    --ob-border-strong: #574838;

    /* Focus. The outer ring inverts to the page tone so the pair still reads
       against either ground. */
    --ob-focus-ring: #E0A44C;
    --ob-focus-ring-offset-color: #16120E;
  }
}

/* ============================================================================
   REDUCED MOTION

   Every platform exposes this preference and the application honors it, so the
   site does too. Animations collapse to near instant state changes rather than
   disappearing: the state still has to change, it just stops traveling.
   ============================================================================ */

@media (prefers-reduced-motion: reduce) {
  :root {
    --ob-motion-feedback: 1ms;
    --ob-motion-panel: 1ms;
    --ob-motion-page: 1ms;
  }
}
