/* GENERATED by Design/tokens/build_tokens.py from tokens.json. Do not edit. */
:root {
  color-scheme: light dark;
  --color-paper: #F8F4EC;  /* App background. ~40% of a screen. */
  --color-ink: #111111;  /* Text, symbols, outlines, primary buttons. ~10%. */
  --color-white: #FFFFFF;  /* Pantry Pal's body, and the card in the light appearance. Never the dark ground's text (that is Paper). */
  --color-label-gray: #D9D9D6;  /* Partial and check-incomplete fill, disabled. ~8%. */
  --color-safety-yellow: #EEFF00;  /* No-known-match fill, the app icon tile. A fill, never a foreground. ~30%. */
  --color-alert-red: #FF3830;  /* Possible-match fill, the alert badge, one glyph beside a destructive word. Text on it is Ink. ~10%. */
  --color-kraft: #D7C3A6;  /* Packaging texture, print, at most one brand moment per screen. ~2%. */
  --color-powder-blue: #A7D8FF;  /* Pantry Pal's magnifier lens only. */
  --color-muted-ink: #5A574F;  /* Secondary text on Paper. Measured 6.6:1 on Paper (AA). */
  --color-muted-paper: #A8A49B;  /* Secondary text on Ink. Measured 7.6:1 on Ink (AAA). */
  --color-ink-14: #11111124;  /* Ink at 14%, for rules and off controls on Paper. Composited it is #D8D4CD, 1.35:1 against Paper: a rule, not a bar. */
  --color-paper-18: #F8F4EC2E;  /* Paper at 18%, for rules and off controls on Ink. Composited it is #3A3A38, 1.66:1 against Ink; the light and dark rules are equally quiet to the eye, not equal in ratio. */
  --color-ground: var(--color-paper);  /* The screen background. */
  --color-surface: var(--color-white);  /* A card: the Little Label, a notice, a field, a row's tile. White on Paper in the light; in the dark it is Ink with an outline, there is no second dark surface. */
  --color-text: var(--color-ink);  /* Text, symbols, outlines, rules that mean something. */
  --color-muted: var(--color-muted-ink);  /* Secondary text: brands, captions, explanations, the word under an unselected tab. Under Increase Contrast it becomes text. */
  --color-rule: var(--color-ink-14);  /* Dividers between rows, the line under a scrolled title, the line above the tab bar. Grouping is rules and space, not boxes. */
  --color-outline: var(--color-ink);  /* The one-point edge of a card, a label, a button, a tile. Every fill and every surface carries it; it is what keeps a 1.3:1 yellow and a 1.1:1 card visible on Paper. */
  --color-fill-no-known-match: var(--color-safety-yellow);  /* NO KNOWN MATCH: every configured source completed and returned nothing. */
  --color-fill-possible-match: var(--color-alert-red);  /* POSSIBLE MATCH: at least one source returned a candidate. */
  --color-fill-partial: var(--color-label-gray);  /* PARTIAL: at least one source failed this check; the text names it. */
  --color-fill-waiting: var(--color-surface);  /* WAITING: saved, not yet checked. The surface colour with the outline, so it reads as a label with nothing in it yet. */
  --color-fill-check-incomplete: var(--color-fill-partial);  /* CHECK INCOMPLETE: the last check did not finish (offline, a source down). The same fill as partial on purpose: in both a source did not answer. Aliased, not repeated, so they cannot drift. */
  --color-on-fill: var(--color-ink);  /* The status word and glyph on the yellow, red, and gray fills, light or dark. */
  --color-on-fill-waiting: var(--color-text);  /* The status word and glyph on the waiting fill: the text colour, because the waiting fill is the surface. */
  --color-action: var(--color-ink);  /* Primary button fill. */
  --color-on-action: var(--color-paper);  /* Primary button label. */
  --color-accent-red-text: var(--color-alert-red);  /* Red as text only at Headline size or larger: 3.3:1 on Paper fails AA below 18 pt bold. Never in Micro, Caption, or Body; the compact status uses fill.possible-match behind an Ink word instead. */
  --color-pal-body: var(--color-white);  /* Pantry Pal's body on both grounds. */
  --color-pal-lens: var(--color-powder-blue);  /* The magnifier lens. */
  --color-packaging: var(--color-kraft);  /* Kraft, for print and one brand moment. */
  --color-scrim: var(--color-ink);  /* Behind a sheet and the caption on the camera, at opacity.scrim. Ink, not black. */
  --color-on-camera: var(--color-paper);  /* The viewfinder brackets and caption over the camera. Paper, not White; White is the Pal's body. */
  --color-control-on: var(--color-ink);  /* A toggle that is on, a selected segment, the cursor and text selection, and the outline of a focused field. Ink, never a tint colour. */
  --color-control-off: var(--color-rule);  /* A toggle that is off: the same quiet as a rule, aliased to it. The thumb carries the outline, so the track can be faint. */
  --color-focus: var(--color-ink);  /* The keyboard focus ring on the website, size.focus-ring wide, outside the element. In the app, focus is the system's. */
  --color-badge: var(--color-alert-red);  /* A count on a tab. The number on it is Ink. */
  --color-destructive: var(--color-alert-red);  /* The glyph beside a destructive word (sym-trash, sym-x) at 16 pt or more: 3.3:1 on Paper, enough for a symbol, not for text. The word itself is text. Never a red word under Headline size. */
  --color-placeholder: var(--color-muted-ink);  /* Placeholder text in a field. */
  --color-loading: var(--color-ink);  /* A progress indicator where the Pal cannot stand in. */
  --color-muted-high-contrast: var(--color-ink);  /* What muted and placeholder become under Increase Contrast. The same value is wired on those tokens as highContrast; this one exists so the website can name it under prefers-contrast. */
  --font-sans: "IBM Plex Sans", system-ui, sans-serif;  /* Sentences: display, headlines, UI labels. Regular, Medium, SemiBold, Bold in Design/fonts/. */
  --font-mono: "IBM Plex Mono", ui-monospace, monospace;  /* Facts: UPC, lot, stamps, sources, eyebrows. Regular and Medium in Design/fonts/. */
  --font-weight-regular: 400;
  --font-weight-medium: 500;
  --font-weight-semibold: 600;
  --font-weight-bold: 700;
  --text-micro: 11px;
  --text-caption: 12px;
  --text-body: 14px;
  --text-body-lg: 16px;
  --text-subtitle: 20px;
  --text-title: 28px;
  --text-headline: 40px;
  --text-display-l: 56px;
  --text-display-xl: 72px;
  --leading-16: 16px;
  --leading-20: 20px;
  --leading-24: 24px;
  --leading-28: 28px;
  --leading-36: 36px;
  --leading-48: 48px;
  --leading-64: 64px;
  --leading-80: 80px;
  --tracking-tight: -0.02;  /* Display sizes. */
  --tracking-normal: 0;
  --tracking-micro: 0.1;  /* Micro, uppercase. */
  --tracking-tagline: 0.18;  /* The tagline under the wordmark, uppercase Mono. */
  --type-display-xl: 700 72px/80px "IBM Plex Sans", system-ui, sans-serif;  /* The wordmark's size on a phone. Onboarding, one line. Capped at accessibility.display-cap. */
  --type-display-xl-tracking: -0.02em;
  --type-display-l: 700 56px/64px "IBM Plex Sans", system-ui, sans-serif;  /* Hero counters, the poster line. Capped at accessibility.display-cap. */
  --type-display-l-tracking: -0.02em;
  --type-headline: 700 40px/48px "IBM Plex Sans", system-ui, sans-serif;  /* Screen titles while the list is at the top. */
  --type-headline-tracking: 0em;
  --type-title: 600 28px/36px "IBM Plex Sans", system-ui, sans-serif;  /* Section titles on a detail screen. Not the product name: that is Subtitle. */
  --type-title-tracking: 0em;
  --type-subtitle: 600 20px/28px "IBM Plex Sans", system-ui, sans-serif;  /* The product name on the Little Label, card titles, the screen title once the list has scrolled. */
  --type-subtitle-tracking: 0em;
  --type-status-word: 700 20px/24px "IBM Plex Sans", system-ui, sans-serif;  /* The word on the Little Label's status block. Subtitle size in Bold, uppercase. */
  --type-status-word-tracking: 0em;
  --type-status-word-transform: uppercase;
  --type-body-lg: 400 16px/24px "IBM Plex Sans", system-ui, sans-serif;  /* Explanations, notice text, settings copy. */
  --type-body-lg-tracking: 0em;
  --type-body: 400 14px/20px "IBM Plex Sans", system-ui, sans-serif;  /* Supporting text, list rows, the Pal's sentence. */
  --type-body-tracking: 0em;
  --type-body-semibold: 600 14px/20px "IBM Plex Sans", system-ui, sans-serif;  /* A row's product name, a notice card's title, a link's word. */
  --type-body-semibold-tracking: 0em;
  --type-caption: 400 12px/16px "IBM Plex Mono", ui-monospace, monospace;  /* Data: UPC, lot, best-by, the checked-at stamp, sources. */
  --type-caption-tracking: 0em;
  --type-micro: 500 11px/16px "IBM Plex Mono", ui-monospace, monospace;  /* Eyebrows, metadata keys, the compact status word, tab labels. Uppercase, never a sentence. */
  --type-micro-tracking: 0.1em;
  --type-micro-transform: uppercase;
  --type-button: 600 14px/20px "IBM Plex Sans", system-ui, sans-serif;  /* The word on a button, primary or secondary. */
  --type-button-tracking: 0em;
  --spacing-4: 4px;
  --spacing-8: 8px;
  --spacing-12: 12px;
  --spacing-16: 16px;
  --spacing-24: 24px;
  --spacing-32: 32px;
  --spacing-48: 48px;
  --spacing-64: 64px;
  --layout-screen-margin: var(--spacing-16);  /* Left and right of every screen, added to the safe-area inset, in every orientation. */
  --layout-card-gap: var(--spacing-24);  /* Between cards and labels in a list. */
  --layout-label-padding: var(--spacing-12);  /* Inside a Little Label. */
  --layout-section-gap: var(--spacing-32);  /* Between sections of a screen. */
  --layout-label-min-width: 240px;  /* The narrowest the Little Label is drawn. Measured 2026-09-06 with Plex Mono Medium 11 at +10% (7.7 pt a character): the stamp line CHECKED 09.05.26  7:42 PM is 25 characters, 193 pt, on a line of its own; the sources line FDA / USDA SOURCES is 139 pt and shares its line with the 53 pt IMFR? chip, 200 pt with the gap. Inside 240 minus twice label-padding and the outline (214) both fit. The stamp never shares a line with the chip: at 240 it would wrap. Every supported phone gives the label more than this in portrait. */
  --layout-content-max: 600px;  /* Widest a column of content gets. In landscape and on the widest phones the column is centred with the ground either side; lists and the label never stretch past it. */
  --breakpoint-phone: 430px;  /* The widest phone. Below it, one column. */
  --breakpoint-tablet: 768px;  /* Two columns from here. */
  --breakpoint-desktop: 1440px;  /* Board and page width. */
  --measure: 60ch;  /* Body copy under this many characters a line on a phone; emitted as 60ch (board 06). */
  --radius-chip: 4px;  /* IMFR? box, word tags, the compact status on its fill. */
  --radius-label: 8px;  /* The status block, a field, a row's tile. */
  --radius-card: 12px;  /* The label card, any card. */
  --radius-button: var(--radius-label);  /* Buttons share the status block's radius. */
  --ratio-icon-mask: 0.22;  /* The app icon's corner as a fraction of its side, for the website and diagrams; iOS applies its own mask (board 08). */
  --ratio-icon-safe-area: 0.8;  /* The middle fraction of the app icon the check stays inside (board 08). */
  --size-symbol-min: 16px;  /* Smallest a symbol is drawn, 1 pt a cell. Below it, use text. The glyph on a label, in a row, beside a word. */
  --size-symbol-md: 32px;  /* 2 pt a cell. Tab bar symbols, the back chevron and toolbar actions inside their 44 pt targets, a card's leading mark. */
  --size-symbol-lg: 48px;  /* 3 pt a cell. Feature spots, the scan sheet's barcode. */
  --size-symbol-xl: 64px;  /* 4 pt a cell. Empty states without the Pal, print. */
  --size-tab-symbol: var(--size-symbol-md);  /* The four tab bar marks. */
  --size-pal-min: 28px;  /* Smallest Pantry Pal is drawn, 1 pt a cell: inline beside a caption, in a row. At 16 pt use sym-leaf. */
  --size-pal-sm: var(--size-pal-min);  /* Pantry Pal inline, beside a sentence in Body. */
  --size-pal-md: 56px;  /* 2 pt a cell. The corner of a screen, empty and loading states, a footer. */
  --size-pal-lg: 84px;  /* 3 pt a cell. Onboarding and the poster, the one place it is a hero. */
  --size-hairline: 0.5px;  /* A rule in the app: half a point, one device pixel at 2x, one and a half at 3x, where iOS draws it as one. Boards draw it at 1 px because they render at 1x. */
  --size-outline: 1px;  /* The edge of a card, label, button, or tile. Never optional: see color.semantic.outline. */
  --size-icon-cell: 32px;  /* One cell of the app icon at 1024. */
  --size-tap-target: 44px;  /* The smallest hit area, per Apple. */
  --size-row-tile: 40px;  /* The square tile at the start of a row, holding the product image or sym-box at symbol-min. */
  --size-badge: 16px;  /* The count badge on a tab: a 16 pt circle, Ink number in Micro, offset to the top right of the symbol. */
  --size-ad-banner: 50px;  /* The height of the anchored banner ad when one is loaded (AdSizeBanner, 320 by 50). The slot is zero tall until then and never sits beside a label. */
  --size-focus-ring: 2px;  /* The website's keyboard focus ring, outside the element, in color.semantic.focus. */
  --opacity-pressed-action: 0.82;  /* Primary button while pressed. */
  --opacity-pressed-surface: 0.7;  /* Secondary button and rows while pressed. */
  --opacity-disabled: 0.45;  /* A control that cannot be used. */
  --opacity-inactive: 0.45;  /* A tab that is not selected: its symbol. The word under it is muted, not an opacity. */
  --opacity-scrim: 0.55;  /* Ink behind a sheet or a caption over the camera. */
  --opacity-on-camera: 0.9;  /* Paper brackets and caption over the camera. */
  --opacity-hidden: 0.0;  /* The start of a reveal; Reduce Motion animates only this. */
  --opacity-scrim-reduce-transparency: 1.0;  /* The scrim under Reduce Transparency: opaque Ink. */
  --grid-symbol-cells: 16;  /* Every sym-*.grid. */
  --grid-pal-cells: 28;  /* pal-body.grid and its overlays. */
  --grid-icon-cells: 32;  /* check.grid and icon.grid; 32 px per cell at 1024. */
  --elevation-none: 0;  /* Nothing casts a shadow, on either ground. A card is separated by its outline, a sheet by the scrim. This token exists so the rule is in the file and a shadow is a deliberate change here, not a drift. */
  --motion-pal-blink-frames: 2;  /* The most frames a Pal blink may have. */
  --motion-reveal-scale: 0.94;  /* Where a card or status starts when it swaps identity; Reduce Motion drops the scale and keeps the fade. */
  --motion-pal-blink-frame-ms: 100;  /* How long each blink frame shows. Chosen 2026-09-06, not yet seen on a device: a decision, not a measurement. */
}

/* Dark appearance: ground, text, rules, and controls swap Paper for Ink; a card swaps White for Ink. */
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) {
  --color-ground: var(--color-ink);
  --color-surface: var(--color-ink);
  --color-text: var(--color-paper);
  --color-muted: var(--color-muted-paper);
  --color-rule: var(--color-paper-18);
  --color-outline: var(--color-paper);
  --color-fill-waiting: var(--color-ink);
  --color-on-fill: var(--color-ink);
  --color-on-fill-waiting: var(--color-paper);
  --color-action: var(--color-paper);
  --color-on-action: var(--color-ink);
  --color-control-on: var(--color-paper);
  --color-control-off: var(--color-paper-18);
  --color-focus: var(--color-paper);
  --color-placeholder: var(--color-muted-paper);
  --color-loading: var(--color-paper);
  --color-muted-high-contrast: var(--color-paper);
} }
:root[data-theme="dark"] {
  --color-ground: var(--color-ink);
  --color-surface: var(--color-ink);
  --color-text: var(--color-paper);
  --color-muted: var(--color-muted-paper);
  --color-rule: var(--color-paper-18);
  --color-outline: var(--color-paper);
  --color-fill-waiting: var(--color-ink);
  --color-on-fill: var(--color-ink);
  --color-on-fill-waiting: var(--color-paper);
  --color-action: var(--color-paper);
  --color-on-action: var(--color-ink);
  --color-control-on: var(--color-paper);
  --color-control-off: var(--color-paper-18);
  --color-focus: var(--color-paper);
  --color-placeholder: var(--color-muted-paper);
  --color-loading: var(--color-paper);
  --color-muted-high-contrast: var(--color-paper);
}

/* Increase Contrast. */
@media (prefers-contrast: more) {
  :root {
    --color-muted: var(--color-ink);
    --color-placeholder: var(--color-ink);
  }
  @media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) {
    --color-muted: var(--color-paper);
    --color-placeholder: var(--color-paper);
  } }
  :root[data-theme="dark"] {
    --color-muted: var(--color-paper);
    --color-placeholder: var(--color-paper);
  }
}

/* Type styles as classes. */
.type-display-xl { font: var(--type-display-xl); letter-spacing: var(--type-display-xl-tracking); }
.type-display-l { font: var(--type-display-l); letter-spacing: var(--type-display-l-tracking); }
.type-headline { font: var(--type-headline); letter-spacing: var(--type-headline-tracking); }
.type-title { font: var(--type-title); letter-spacing: var(--type-title-tracking); }
.type-subtitle { font: var(--type-subtitle); letter-spacing: var(--type-subtitle-tracking); }
.type-status-word { font: var(--type-status-word); letter-spacing: var(--type-status-word-tracking); text-transform: uppercase; }
.type-body-lg { font: var(--type-body-lg); letter-spacing: var(--type-body-lg-tracking); }
.type-body { font: var(--type-body); letter-spacing: var(--type-body-tracking); }
.type-body-semibold { font: var(--type-body-semibold); letter-spacing: var(--type-body-semibold-tracking); }
.type-caption { font: var(--type-caption); letter-spacing: var(--type-caption-tracking); }
.type-micro { font: var(--type-micro); letter-spacing: var(--type-micro-tracking); text-transform: uppercase; }
.type-button { font: var(--type-button); letter-spacing: var(--type-button-tracking); }

/* Keyboard focus: one ring, outside the element, no shadow. */
:focus-visible { outline: var(--size-focus-ring) solid var(--color-focus); outline-offset: 2px; }
