@aglyn/aglyn 1.0.0-beta.277 → 1.0.0-beta.283

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/package.json +11 -11
  2. package/src/lib/app-utils/account-preferences.d.ts +146 -11
  3. package/src/lib/app-utils/account-preferences.js +266 -18
  4. package/src/lib/app-utils/account-preferences.js.map +1 -1
  5. package/src/lib/app-utils/activity-presenter.js +2 -26
  6. package/src/lib/app-utils/activity-presenter.js.map +1 -1
  7. package/src/lib/app-utils/activity-type-labels.d.ts +18 -0
  8. package/src/lib/app-utils/activity-type-labels.js +46 -0
  9. package/src/lib/app-utils/activity-type-labels.js.map +1 -0
  10. package/src/lib/app-utils/artifact-provenance.d.ts +3 -1
  11. package/src/lib/app-utils/artifact-provenance.js.map +1 -1
  12. package/src/lib/app-utils/bundle-intent.d.ts +37 -0
  13. package/src/lib/app-utils/bundle-intent.js +47 -3
  14. package/src/lib/app-utils/bundle-intent.js.map +1 -1
  15. package/src/lib/app-utils/compose-reusable-components.d.ts +17 -1
  16. package/src/lib/app-utils/compose-reusable-components.js +80 -5
  17. package/src/lib/app-utils/compose-reusable-components.js.map +1 -1
  18. package/src/lib/app-utils/docs-help-sections.generated.js +13 -1
  19. package/src/lib/app-utils/docs-help-sections.generated.js.map +1 -1
  20. package/src/lib/app-utils/docs-help.generated.d.ts +23 -11
  21. package/src/lib/app-utils/docs-help.generated.js +64 -7
  22. package/src/lib/app-utils/docs-help.generated.js.map +1 -1
  23. package/src/lib/app-utils/docs-index.generated.js +359 -51
  24. package/src/lib/app-utils/docs-index.generated.js.map +1 -1
  25. package/src/lib/app-utils/error-beacon.d.ts +19 -0
  26. package/src/lib/app-utils/error-beacon.js +28 -0
  27. package/src/lib/app-utils/error-beacon.js.map +1 -1
  28. package/src/lib/app-utils/health-report.d.ts +3 -2
  29. package/src/lib/app-utils/health-report.js +22 -2
  30. package/src/lib/app-utils/health-report.js.map +1 -1
  31. package/src/lib/app-utils/native-nav.d.ts +39 -4
  32. package/src/lib/app-utils/native-nav.js +69 -3
  33. package/src/lib/app-utils/native-nav.js.map +1 -1
  34. package/src/lib/app-utils/native-themes.d.ts +28 -17
  35. package/src/lib/app-utils/native-themes.js +123 -45
  36. package/src/lib/app-utils/native-themes.js.map +1 -1
  37. package/src/lib/app-utils/operator-alerts.js +4 -2
  38. package/src/lib/app-utils/operator-alerts.js.map +1 -1
  39. package/src/lib/app-utils/org-permission-editor.d.ts +75 -0
  40. package/src/lib/app-utils/org-permission-editor.js +124 -0
  41. package/src/lib/app-utils/org-permission-editor.js.map +1 -0
  42. package/src/lib/app-utils/org-permissions.d.ts +36 -0
  43. package/src/lib/app-utils/org-permissions.js +78 -26
  44. package/src/lib/app-utils/org-permissions.js.map +1 -1
  45. package/src/lib/app-utils/org-role-grants.d.ts +59 -9
  46. package/src/lib/app-utils/org-role-grants.js +97 -10
  47. package/src/lib/app-utils/org-role-grants.js.map +1 -1
  48. package/src/lib/app-utils/plan-entitlements.d.ts +13 -0
  49. package/src/lib/app-utils/plan-entitlements.js +22 -0
  50. package/src/lib/app-utils/plan-entitlements.js.map +1 -1
  51. package/src/lib/app-utils/release-flags.d.ts +52 -1
  52. package/src/lib/app-utils/release-flags.js +68 -2
  53. package/src/lib/app-utils/release-flags.js.map +1 -1
  54. package/src/lib/app-utils/reusable-component-keys.d.ts +13 -0
  55. package/src/lib/app-utils/reusable-component-keys.js +12 -0
  56. package/src/lib/app-utils/reusable-component-keys.js.map +1 -1
  57. package/src/lib/app-utils/screen-analytics-aggregate.d.ts +34 -0
  58. package/src/lib/app-utils/screen-analytics-aggregate.js +37 -0
  59. package/src/lib/app-utils/screen-analytics-aggregate.js.map +1 -1
  60. package/src/lib/app-utils/site-care.d.ts +35 -0
  61. package/src/lib/app-utils/site-care.js +48 -0
  62. package/src/lib/app-utils/site-care.js.map +1 -1
  63. package/src/lib/app-utils/site-font-pairings.d.ts +59 -0
  64. package/src/lib/app-utils/site-font-pairings.js +514 -0
  65. package/src/lib/app-utils/site-font-pairings.js.map +1 -0
  66. package/src/lib/app-utils/site-starter.d.ts +136 -0
  67. package/src/lib/app-utils/site-starter.js +116 -0
  68. package/src/lib/app-utils/site-starter.js.map +1 -0
  69. package/src/lib/app-utils/stock-photos/stock-photo-picker-server.d.ts +95 -0
  70. package/src/lib/app-utils/stock-photos/stock-photo-picker-server.js +333 -0
  71. package/src/lib/app-utils/stock-photos/stock-photo-picker-server.js.map +1 -0
  72. package/src/lib/app-utils/stock-photos/stock-photo-picker.d.ts +188 -0
  73. package/src/lib/app-utils/stock-photos/stock-photo-picker.js +193 -0
  74. package/src/lib/app-utils/stock-photos/stock-photo-picker.js.map +1 -0
  75. package/src/lib/plugin-manager/feature-plugins.d.ts +32 -0
  76. package/src/lib/plugin-manager/feature-plugins.js +12 -0
  77. package/src/lib/plugin-manager/feature-plugins.js.map +1 -1
  78. package/src/lib/plugin-manager/first-party-plugins.generated.js +27 -1
  79. package/src/lib/plugin-manager/first-party-plugins.generated.js.map +1 -1
  80. package/src/lib/plugin-manager/plugin-entitlements.d.ts +37 -0
  81. package/src/lib/plugin-manager/plugin-entitlements.js +84 -2
  82. package/src/lib/plugin-manager/plugin-entitlements.js.map +1 -1
  83. package/src/lib/plugin-manager/plugin-media-ingest.d.ts +15 -0
  84. package/src/lib/plugin-manager/plugin-media-ingest.js.map +1 -1
  85. package/src/lib/plugin-manager/plugin-permissions.d.ts +7 -0
  86. package/src/lib/plugin-manager/plugin-permissions.js.map +1 -1
  87. package/src/lib/plugin-manager/plugin-product-catalog.d.ts +12 -0
  88. package/src/lib/plugin-manager/plugin-product-catalog.js.map +1 -1
  89. package/src/lib/plugin-manager/plugin-site-starters.d.ts +73 -0
  90. package/src/lib/plugin-manager/plugin-site-starters.js +30 -0
  91. package/src/lib/plugin-manager/plugin-site-starters.js.map +1 -0
  92. package/src/lib/plugin-manager/plugin-sitemap-sections.d.ts +8 -1
  93. package/src/lib/plugin-manager/plugin-sitemap-sections.js.map +1 -1
  94. package/src/lib/plugin-manager/stock-photo-provider.d.ts +12 -0
  95. package/src/lib/plugin-manager/stock-photo-provider.js.map +1 -1
@@ -29,7 +29,8 @@
29
29
  * - `THEME_ACCENTS`: the Accent modes Blue, Violet, Pink and Workspace
30
30
  * brand, exactly. `deriveAccent` returns them for their ring colors and
31
31
  * derives any other color (a picked accent, a workspace's brand) by rule.
32
- * - `THEME_ACCENT_SWATCHES`: the six swatches the Theme settings sheet shows.
32
+ * - `THEME_ACCENT_SWATCHES`: the five swatches the Theme settings sheet shows.
33
+ * - `THEME_RETIRED_ACCENTS`: retired blues, and the brand blue each reads as.
33
34
  * - `THEME_DENSITIES`: Comfortable and Compact.
34
35
  *
35
36
  * Contrast is WCAG 2's relative luminance, as `accessible-shade.ts` (the
@@ -509,28 +510,114 @@ const WARM = deriveThemeFromPreview(THEME_PREVIEWS.warm, THEME_DARK_RAIL_SEEDS.w
509
510
  source: 'figma'
510
511
  }
511
512
  ];
513
+ /**
514
+ * The Aglyn theme on a PHONE: the brand's bold surfaces (AGL-3990). Zach chose
515
+ * the bold look for the phone on 2026-10-19 and kept every other form factor
516
+ * on the console's neutral scheme ("I don't like this light blue everywhere",
517
+ * on the desktop): so only an Aglyn or Aglyn POS PHONE, in the Aglyn theme with
518
+ * the brand blue as its accent, draws these. A tablet, an iPad, a Mac, a
519
+ * Windows or Mac desktop and the POS register keep `THEME_PALETTES`' Aglyn;
520
+ * every other theme, a picked accent and a white-label brand look the same on
521
+ * a phone as anywhere else.
522
+ *
523
+ * The values are Figma's Theme mode 223:0, "Aglyn · phone (bold) 10/19", the
524
+ * explicit mode on every frame of the Native App file's Phone page (2:7). A
525
+ * sky-tinted ground under white panels in light; navy in dark. The strong
526
+ * border is the brand blue at 40% over the white surface, flattened. The
527
+ * statuses are the console's intents, tinted to sit on the sky ground; info's
528
+ * ink in light is the brand's ink, and in dark it is the accent's dark text
529
+ * shade (#4DC8FF), never the fill (Zach, 2026-10-19: blue as text on a dark
530
+ * ground is the console's `primary.dark`).
531
+ */ const PHONE_AGLYN_LIGHT = _extends({
532
+ canvas: '#edf9ff',
533
+ surface: '#ffffff',
534
+ raised: '#ffffff',
535
+ sunken: '#e6f7ff',
536
+ rail: '#e0f6ff',
537
+ hover: '#f2fbff',
538
+ titlebar: '#f7fdff',
539
+ borderSubtle: '#dbf4ff',
540
+ borderStrong: '#66d0ff',
541
+ textPrimary: '#19202a',
542
+ textSecondary: '#404c5c',
543
+ textTertiary: '#5d6d85',
544
+ successBg: '#e2f2e3',
545
+ successFg: '#306f33',
546
+ warningBg: '#ffebd1',
547
+ warningFg: '#975400',
548
+ dangerBg: '#fbdddc',
549
+ dangerFg: '#ba1c18',
550
+ infoBg: '#ccefff',
551
+ infoFg: '#07171f',
552
+ newBg: '#fae0fe',
553
+ newFg: '#a604c2',
554
+ scrim: '#0f172152'
555
+ }, DANGER_LIGHT);
556
+ const PHONE_AGLYN_DARK = _extends({
557
+ canvas: '#07181f',
558
+ surface: '#0d2029',
559
+ raised: '#152b35',
560
+ sunken: '#1a323d',
561
+ rail: '#061319',
562
+ hover: '#213a45',
563
+ titlebar: '#0a1c24',
564
+ borderSubtle: '#263f4a',
565
+ borderStrong: '#395460',
566
+ textPrimary: '#f2f7fb',
567
+ textSecondary: '#c1d0d7',
568
+ textTertiary: '#8ba5b1',
569
+ successBg: '#1d4433',
570
+ successFg: '#72c275',
571
+ warningBg: '#4c432f',
572
+ warningFg: '#ffab40',
573
+ dangerBg: '#452228',
574
+ dangerFg: '#ee7b78',
575
+ infoBg: '#0a3d54',
576
+ infoFg: '#4dc8ff',
577
+ newBg: '#442860',
578
+ newFg: '#ea7cfc',
579
+ scrim: '#0000008c'
580
+ }, DANGER_DARK);
581
+ /** The Aglyn theme's grounds on a phone, light and dark (Figma Theme mode 223:0): see `PHONE_AGLYN_LIGHT`. */ export const THEME_PHONE_AGLYN = {
582
+ light: PHONE_AGLYN_LIGHT,
583
+ dark: PHONE_AGLYN_DARK
584
+ };
585
+ /** The accent the phone's bold look belongs to: the brand blue, the Aglyn theme's own. */ export const THEME_PHONE_BOLD_ACCENT = AGLYN_ACCENT;
512
586
  // ── Accents ─────────────────────────────────────────────────────────────────
513
587
  /** The surfaces an accent's text is held to. */ const LIGHT_PAGE = AGLYN_LIGHT.surface;
514
588
  const DARK_PAGE = AGLYN_DARK.surface;
515
- /** Text and icons on a fill that keeps a light brand color. */ const DARK_ON_LIGHTNESS = 0.08;
516
589
  function accentText(raw) {
517
590
  return {
518
591
  textLight: walkToContrast(raw, LIGHT_PAGE, THEME_TEXT_CONTRAST, -1),
519
592
  textDark: walkToContrast(raw, DARK_PAGE, THEME_TEXT_CONTRAST, 1)
520
593
  };
521
594
  }
522
- /** Figma's Accent modes, exactly as the Accent collection holds them. */ const FIGMA_ACCENTS = [
595
+ /**
596
+ * Figma's Accent modes, exactly as the Accent collection holds them.
597
+ *
598
+ * Blue IS the console's primary (AGL-3990). Zach, 2026-10-19: "Just like the
599
+ * web app the solid buttons should have white text and the brand blue when it
600
+ * is on a light background should use the other shade of blue". So the fill
601
+ * is `primary.main` #00B0FF under `contrastText` white (the console's own
602
+ * pairing, knowingly under 4.5:1, AGL-1293), the text shades are
603
+ * `primary.dark` per scheme (#0077AD light, #4DC8FF dark: what MuiLink and the
604
+ * text and outlined MuiButton labels draw, AGL-1186), and the hover fill is
605
+ * `primary.hover`. #0073AE is retired. A mode that names its own text shades
606
+ * keeps them; the others walk their ring to 4.5:1.
607
+ */ const FIGMA_ACCENTS = [
523
608
  {
524
609
  id: 'blue',
525
610
  label: 'Blue',
526
611
  accent: {
527
- fill: '#0073ae',
528
- icon: '#0091d5',
612
+ fill: '#00b0ff',
613
+ icon: '#00b0ff',
529
614
  soft: '#00b0ff29',
530
615
  ring: '#00b0ff',
531
616
  on: '#ffffff',
532
- hover: '#00669b',
533
- pressed: '#005884'
617
+ hover: '#0077ad',
618
+ pressed: '#006999',
619
+ textLight: '#0077ad',
620
+ textDark: '#4dc8ff'
534
621
  }
535
622
  },
536
623
  {
@@ -554,7 +641,7 @@ function accentText(raw) {
554
641
  icon: '#c93ae3',
555
642
  soft: '#e040fb29',
556
643
  ring: '#e040fb',
557
- on: '#1e0424',
644
+ on: '#ffffff',
558
645
  hover: '#cc2ee6',
559
646
  pressed: '#b523cf'
560
647
  }
@@ -573,20 +660,22 @@ function accentText(raw) {
573
660
  }
574
661
  }
575
662
  ];
576
- /** The named accents, Figma's own values with their text shades. */ export const THEME_ACCENTS = FIGMA_ACCENTS.map(({ id, label, accent })=>({
663
+ /** The named accents, Figma's own values with their text shades (their own where they name them). */ export const THEME_ACCENTS = FIGMA_ACCENTS.map(({ id, label, accent })=>({
577
664
  id,
578
665
  label,
579
- accent: _extends({}, accent, accentText(accent.ring))
666
+ accent: _extends({}, accentText(accent.ring), accent)
580
667
  }));
581
- /** The accent picker's swatches, in Figma's order. */ export const THEME_ACCENT_SWATCHES = [
668
+ /**
669
+ * Blues the brand has retired (AGL-3990), and the color each now reads as. The
670
+ * picker's old "Brand blue" swatch was #0073AE; an accent an account saved
671
+ * from it (`appTheme.accent`) reads as the brand blue, #00B0FF.
672
+ */ export const THEME_RETIRED_ACCENTS = {
673
+ '#0073ae': '#00b0ff'
674
+ };
675
+ /** The accent picker's swatches, in Figma's order. #00B0FF is the one brand blue (AGL-3990), so "Sky" went. */ export const THEME_ACCENT_SWATCHES = [
582
676
  {
583
677
  id: 'aglynBlue',
584
678
  label: 'Brand blue',
585
- color: '#0073ae'
586
- },
587
- {
588
- id: 'sky',
589
- label: 'Sky',
590
679
  color: '#00b0ff'
591
680
  },
592
681
  {
@@ -613,21 +702,21 @@ function accentText(raw) {
613
702
  /**
614
703
  * Every control's shade of one accent color (AGL-3950).
615
704
  *
616
- * A color one of Figma's Accent modes is drawn from (its ring) answers with
617
- * that mode exactly: Figma wins where it defines a value. Any other color is
618
- * derived:
705
+ * A retired blue (`THEME_RETIRED_ACCENTS`) reads as the color that replaced
706
+ * it. A color one of Figma's Accent modes is drawn from (its ring) answers
707
+ * with that mode exactly: Figma wins where it defines a value. Blue, the brand
708
+ * blue, is one of them, and wears the console's own pairing. Any other color
709
+ * is derived:
619
710
  *
620
- * 1. White text on the color already reads (4.5:1): the fill is the color,
621
- * `on` is white.
622
- * 2. The color carries white text only at the large-text bar (3:1 up to
623
- * 4.5:1) and near-black text reads on it at 4.5:1: the fill KEEPS the
624
- * brand color and `on` flips to near-black, the color's own hue at 8%
625
- * lightness. Darkening such a color far enough for white text moves it
626
- * visibly off the brand; this is Figma's Pink (#E040FB, 3.34:1). The 3:1
627
- * threshold is the one that sends Pink here and Blue (2.43:1) and the
628
- * workspace green (2.99:1) to rule 3, as Figma draws them.
629
- * 3. Otherwise the fill is the color darkened in 2% HSL-lightness steps until
630
- * white reaches 4.5:1, and `on` is white.
711
+ * 1. White text on the color reads at 3:1 or better: the fill is the color,
712
+ * `on` is white. Content on a filled control is always white (Zach,
713
+ * 2026-10-19: "Just like the web app the solid buttons should have white
714
+ * text"), and a brand color white reads on at the large-text bar keeps
715
+ * its own fill rather than moving off the brand, the console's own
716
+ * pairing; this is Figma's Pink (#E040FB, 3.34:1).
717
+ * 2. Otherwise the fill is the color darkened in 2% HSL-lightness steps until
718
+ * white reaches 4.5:1, and `on` is white. The 3:1 threshold is the one
719
+ * that sends the workspace green (2.99:1) here, as Figma draws it.
631
720
  *
632
721
  * hover and pressed are the fill 4% and 8% darker (HSL lightness), soft is
633
722
  * the color at 16% alpha, ring is the color, icon is the color darkened in 2%
@@ -635,30 +724,19 @@ function accentText(raw) {
635
724
  * walk it (darker on light, lighter on dark) until 4.5:1 on that surface.
636
725
  * Null for anything that is not `#RRGGBB`.
637
726
  */ export function deriveAccent(color) {
727
+ var _THEME_RETIRED_ACCENTS_color_toLowerCase;
638
728
  if (!isThemeColor(color)) return null;
639
- const raw = color.toLowerCase();
729
+ const raw = (_THEME_RETIRED_ACCENTS_color_toLowerCase = THEME_RETIRED_ACCENTS[color.toLowerCase()]) != null ? _THEME_RETIRED_ACCENTS_color_toLowerCase : color.toLowerCase();
640
730
  const named = THEME_ACCENTS.find((entry)=>entry.accent.ring === raw);
641
731
  if (named) return _extends({}, named.accent);
642
732
  const white = '#ffffff';
643
- const onWhite = themeContrast(raw, white);
644
- let fill = raw;
645
- let on = white;
646
- if (onWhite < THEME_TEXT_CONTRAST) {
647
- const rgb = channels(raw);
648
- const [h, s] = toHsl(rgb);
649
- const nearBlack = fromHsl(h, Math.min(s, 0.8), DARK_ON_LIGHTNESS);
650
- if (onWhite >= THEME_NON_TEXT_CONTRAST && themeContrast(raw, nearBlack) >= THEME_TEXT_CONTRAST) {
651
- on = nearBlack;
652
- } else {
653
- fill = walkToContrast(raw, white, THEME_TEXT_CONTRAST, -1);
654
- }
655
- }
733
+ const fill = themeContrast(raw, white) >= THEME_NON_TEXT_CONTRAST ? raw : walkToContrast(raw, white, THEME_TEXT_CONTRAST, -1);
656
734
  return _extends({
657
735
  fill,
658
736
  icon: walkToContrast(raw, LIGHT_PAGE, THEME_NON_TEXT_CONTRAST, -1),
659
737
  soft: withAlpha(raw, 0.16),
660
738
  ring: raw,
661
- on,
739
+ on: white,
662
740
  hover: shiftLightness(fill, -0.04),
663
741
  pressed: shiftLightness(fill, -0.08)
664
742
  }, accentText(raw));
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/native-themes.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * THE NATIVE APPS' THEMES, AS DATA (AGL-3950).\n *\n * The values are read from the Native App Figma file's variables (Themes,\n * page 2:8; collections Aglyn / Theme, Aglyn / Appearance, Aglyn / Accent,\n * Aglyn / Brand and Aglyn / Density), which win wherever they define a value:\n *\n * - `THEME_PALETTES`: Aglyn is the look that ships, the console's resolved\n * MUI scheme (see `AGLYN_LIGHT`); Quiet, Night and High contrast, light and\n * dark, are exactly as Figma's Theme modes hold them. Ocean and Warm exist in Figma\n * only as four-color previews (rail, surface, canvas, accent), so their\n * full palettes are derived here by `deriveThemeFromPreview`, the rule\n * below, and drawn back into Figma as swatch boards.\n * - `THEME_ACCENTS`: the Accent modes Blue, Violet, Pink and Workspace\n * brand, exactly. `deriveAccent` returns them for their ring colors and\n * derives any other color (a picked accent, a workspace's brand) by rule.\n * - `THEME_ACCENT_SWATCHES`: the six swatches the Theme settings sheet shows.\n * - `THEME_DENSITIES`: Comfortable and Compact.\n *\n * Contrast is WCAG 2's relative luminance, as `accessible-shade.ts` (the\n * console's theme) measures it; lightness steps are HSL lightness, as it\n * walks them.\n *\n * NO IMPORTS, on purpose: the native apps read the values, the types and the\n * answers of these functions through the generated contracts.\n */\n\n/** A theme the apps offer. */\nexport type ThemePaletteId = 'aglyn' | 'quiet' | 'night' | 'ocean' | 'warm' | 'highContrast'\n\n/** One appearance of a theme: every color a native screen draws from. */\nexport interface ThemeColors {\n canvas: string\n surface: string\n raised: string\n sunken: string\n rail: string\n hover: string\n titlebar: string\n borderSubtle: string\n borderStrong: string\n textPrimary: string\n textSecondary: string\n textTertiary: string\n successBg: string\n successFg: string\n warningBg: string\n warningFg: string\n dangerBg: string\n dangerFg: string\n infoBg: string\n infoFg: string\n newBg: string\n newFg: string\n /** `#RRGGBBAA`: the wash behind a sheet. */\n scrim: string\n /** A destructive button's fill, and its hover and pressed fills. */\n dangerFill: string\n dangerFillHover: string\n dangerFillPressed: string\n}\n\n/**\n * Where a theme's values come from: the console's resolved MUI scheme (the\n * look that ships, Aglyn), Figma's variables, or derived from a Figma preview.\n */\nexport type ThemeSource = 'console' | 'figma' | 'derived'\n\nexport interface ThemePalette {\n id: ThemePaletteId\n /** The name a picker shows; empty when `brandLabel` says to show the configured brand name. */\n label: string\n /** The theme is named after the product: show the build's configured brand name (white-label safe). */\n brandLabel?: boolean\n light: ThemeColors\n dark: ThemeColors\n /** The theme's own accent (\"Accent: Theme\"), as a raw color for `deriveAccent`. */\n accent: string\n /** Night is the same in both appearances: the theme is always dark. */\n alwaysDark?: boolean\n source: ThemeSource\n}\n\n/** An accent, as every control that paints it reads it. */\nexport interface ThemeAccent {\n /** A filled control's background. */\n fill: string\n /** An icon or an indicator in the accent, on the light surfaces. */\n icon: string\n /** `#RRGGBBAA`: a selected row's or a chip's wash. */\n soft: string\n /** The focus ring: the raw accent. */\n ring: string\n /** Text and icons ON `fill`. */\n on: string\n hover: string\n pressed: string\n /** The accent as text on the light surface, at 4.5:1. */\n textLight: string\n /** The accent as text on the dark surface, at 4.5:1. */\n textDark: string\n}\n\n/** An accent the Theme settings sheet offers by name (Figma's Accent modes). */\nexport interface ThemeNamedAccent {\n id: string\n label: string\n accent: ThemeAccent\n}\n\n/** A swatch of the accent picker. */\nexport interface ThemeAccentSwatch {\n id: string\n label: string\n color: string\n}\n\nexport type ThemeDensityId = 'comfortable' | 'compact'\n\n/** One density's sizes, in points (Apple) and dp (Android). */\nexport interface ThemeDensity {\n id: ThemeDensityId\n label: string\n space2: number\n space4: number\n space6: number\n space8: number\n space10: number\n space12: number\n space16: number\n space20: number\n space24: number\n space32: number\n space40: number\n space48: number\n space64: number\n radiusXs: number\n radiusSm: number\n radiusMd: number\n radiusLg: number\n radiusXl: number\n radius2xl: number\n radiusFull: number\n rowHeight: number\n controlHeight: number\n listRowHeight: number\n phoneRowHeight: number\n titlebarHeight: number\n}\n\n/** The four colors Figma previews a theme by. */\nexport interface ThemePreview {\n rail: string\n surface: string\n canvas: string\n accent: string\n}\n\n// ── Color arithmetic ────────────────────────────────────────────────────────\n\nconst HEX = /^#([0-9a-f]{6})([0-9a-f]{2})?$/i\n\n/** `[r, g, b]` in 0–255, or null for anything that is not `#RRGGBB(AA)`. */\nfunction channels(color: string): [number, number, number] | null {\n const match = HEX.exec(color ?? '')\n if (!match) return null\n const value = match[1]\n return [0, 2, 4].map((index) => parseInt(value.slice(index, index + 2), 16)) as [number, number, number]\n}\n\nfunction hexOf(rgb: readonly number[]): string {\n return `#${rgb\n .map((value) => Math.round(Math.min(255, Math.max(0, value))).toString(16).padStart(2, '0'))\n .join('')}`\n}\n\n/** Whether a value is `#RRGGBB`. */\nexport function isThemeColor(value: unknown): value is string {\n return typeof value === 'string' && /^#[0-9a-f]{6}$/i.test(value)\n}\n\nfunction linear(channel: number): number {\n const value = channel / 255\n return value <= 0.03928 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4\n}\n\nfunction luminance(rgb: readonly number[]): number {\n return 0.2126 * linear(rgb[0]) + 0.7152 * linear(rgb[1]) + 0.0722 * linear(rgb[2])\n}\n\n/** WCAG 2 contrast between two `#RRGGBB` colors, as the console's `contrastRatio` measures it. */\nexport function themeContrast(a: string, b: string): number {\n const x = channels(a)\n const y = channels(b)\n if (!x || !y) return 1\n const [high, low] = [luminance(x), luminance(y)].sort((p, q) => q - p)\n return (high + 0.05) / (low + 0.05)\n}\n\nfunction toHsl([r, g, b]: readonly number[]): [number, number, number] {\n const [red, green, blue] = [r / 255, g / 255, b / 255]\n const max = Math.max(red, green, blue)\n const min = Math.min(red, green, blue)\n const lightness = (max + min) / 2\n if (max === min) return [0, 0, lightness]\n const delta = max - min\n const saturation = lightness > 0.5 ? delta / (2 - max - min) : delta / (max + min)\n let hue: number\n if (max === red) hue = (green - blue) / delta + (green < blue ? 6 : 0)\n else if (max === green) hue = (blue - red) / delta + 2\n else hue = (red - green) / delta + 4\n return [hue / 6, saturation, lightness]\n}\n\nfunction fromHsl(hue: number, saturation: number, lightness: number): string {\n const l = Math.min(1, Math.max(0, lightness))\n if (saturation === 0) return hexOf([l * 255, l * 255, l * 255])\n const q = l < 0.5 ? l * (1 + saturation) : l + saturation - l * saturation\n const p = 2 * l - q\n const channel = (t: number) => {\n let x = t\n if (x < 0) x += 1\n if (x > 1) x -= 1\n if (x < 1 / 6) return p + (q - p) * 6 * x\n if (x < 1 / 2) return q\n if (x < 2 / 3) return p + (q - p) * (2 / 3 - x) * 6\n return p\n }\n return hexOf([channel(hue + 1 / 3) * 255, channel(hue) * 255, channel(hue - 1 / 3) * 255])\n}\n\n/** `color` with its HSL lightness moved by `delta` (−1 to 1). */\nfunction shiftLightness(color: string, delta: number): string {\n const rgb = channels(color)\n if (!rgb) return color\n const [h, s, l] = toHsl(rgb)\n return fromHsl(h, s, l + delta)\n}\n\n/**\n * `color` walked in 2% HSL-lightness steps (darker, or lighter) until it\n * reaches `minContrast` against `against`; the pole when nothing reaches it.\n */\nfunction walkToContrast(color: string, against: string, minContrast: number, direction: -1 | 1): string {\n let candidate = color\n for (let step = 0; step <= 50; step += 1) {\n candidate = shiftLightness(color, direction * 0.02 * step)\n if (themeContrast(candidate, against) >= minContrast) return candidate\n }\n return candidate\n}\n\n/** `from` moved `amount` (0–1) of the way to `to`, per sRGB channel. */\nexport function themeLerp(from: string, to: string, amount: number): string {\n const a = channels(from)\n const b = channels(to)\n if (!a || !b) return from\n return hexOf(a.map((value, index) => value + (b[index] - value) * amount))\n}\n\n/** `color` with an alpha byte: `#RRGGBBAA`. */\nfunction withAlpha(color: string, alpha: number): string {\n const rgb = channels(color)\n if (!rgb) return color\n return `${hexOf(rgb)}${Math.round(alpha * 255).toString(16).padStart(2, '0')}`\n}\n\n// ── Themes ──────────────────────────────────────────────────────────────────\n\n/** Text that must read on every surface: WCAG AA. */\nexport const THEME_TEXT_CONTRAST = 4.5\n\n/** An icon, a border or an indicator: WCAG AA for non-text. */\nexport const THEME_NON_TEXT_CONTRAST = 3\n\nconst DANGER_LIGHT = { dangerFill: '#b3261e', dangerFillHover: '#9c211a', dangerFillPressed: '#861c16' }\nconst DANGER_DARK = { dangerFill: '#c93a31', dangerFillHover: '#b3261e', dangerFillPressed: '#9c211a' }\n\n/**\n * Figma's Aglyn mode as it stood before that mode moved to the console's\n * scheme (2026-10-19): the base Quiet's status colors and the Ocean and Warm\n * derivations build on, as the native shells' themes already do. Kept by\n * value, so moving the shipping Aglyn look moves nothing else.\n */\nconst FIGMA_AGLYN_LIGHT: ThemeColors = {\n canvas: '#f5f5f5',\n surface: '#ffffff',\n raised: '#ffffff',\n sunken: '#edf0f4',\n rail: '#e6eaf0',\n hover: '#eef1f5',\n titlebar: '#fafbfc',\n borderSubtle: '#e3e7ec',\n borderStrong: '#c8d0d9',\n textPrimary: '#1f2329',\n textSecondary: '#5e6773',\n textTertiary: '#7a838e',\n successBg: '#e5f3e6',\n successFg: '#2e6f31',\n warningBg: '#fff0da',\n warningFg: '#85560f',\n dangerBg: '#fce6e5',\n dangerFg: '#a8231e',\n infoBg: '#e3f0fb',\n infoFg: '#10548f',\n newBg: '#f5e4f9',\n newFg: '#8e2aa1',\n scrim: '#0f172152',\n ...DANGER_LIGHT,\n}\n\nconst FIGMA_AGLYN_DARK: ThemeColors = {\n canvas: '#161c21',\n surface: '#1e262e',\n raised: '#27313b',\n sunken: '#12181d',\n rail: '#0f1418',\n hover: '#2a343e',\n titlebar: '#1a2128',\n borderSubtle: '#2e3944',\n borderStrong: '#45525f',\n textPrimary: '#f3f5f7',\n textSecondary: '#b3bcc6',\n textTertiary: '#8a96a3',\n successBg: '#1b3320',\n successFg: '#82c785',\n warningBg: '#3a2c14',\n warningFg: '#ffc479',\n dangerBg: '#3b1a1a',\n dangerFg: '#ed7876',\n infoBg: '#132c42',\n infoFg: '#5da1dc',\n newBg: '#3a1e44',\n newFg: '#e979fc',\n scrim: '#0000008c',\n ...DANGER_DARK,\n}\n\n/** A light and a dark palette. */\nexport interface ThemeColorsPair {\n light: ThemeColors\n dark: ThemeColors\n}\n\n/**\n * The palettes `deriveThemeFromPreview` starts from (the original Figma Aglyn\n * mode), exported so each platform can port the derivation and replay its\n * cases against the same base.\n */\nexport const THEME_FIGMA_BASE: ThemeColorsPair = { light: FIGMA_AGLYN_LIGHT, dark: FIGMA_AGLYN_DARK }\n\n/**\n * The Aglyn theme is the look that ships: the console's resolved MUI scheme\n * (`getConsoleTheme`, the same scheme the native shared tokens are generated\n * from), with translucent text, dividers and hover composited onto the paper\n * they sit on, and the status colors as the console's MUI Alert draws them\n * (standard: the intent's light shade lightened 90% for the ground and\n * darkened 60% for the text in light, the reverse in dark). Where the console\n * scheme has no such color (sunken, rail, titlebar, the strong border, the\n * \"new\" status, the scrim) Figma's Aglyn mode supplies it, and so does the\n * danger FILL: the console's `error.main` carries white text at 4.36:1, under\n * the 4.5:1 a button label owes. `apps/console/specs/native-theme-aglyn.spec.ts`\n * holds these to the console's scheme.\n */\nconst AGLYN_LIGHT: ThemeColors = {\n ...FIGMA_AGLYN_LIGHT,\n canvas: '#f5f5f5',\n surface: '#ffffff',\n raised: '#ffffff',\n hover: '#f5f5f5',\n borderSubtle: '#e0e0e0',\n textPrimary: '#212121',\n textSecondary: '#666666',\n textTertiary: '#9e9e9e',\n successBg: '#f0f8f1',\n successFg: '#2c4c2e',\n warningBg: '#fff8ef',\n warningFg: '#664a28',\n dangerBg: '#fceeed',\n dangerFg: '#5c2220',\n infoBg: '#ecf4fb',\n infoFg: '#1c3a56',\n}\n\nconst AGLYN_DARK: ThemeColors = {\n ...FIGMA_AGLYN_DARK,\n canvas: '#161c21',\n surface: '#2a3440',\n raised: '#2a3440',\n hover: '#3b444f',\n borderSubtle: '#444c57',\n textPrimary: '#ffffff',\n textSecondary: '#bfc2c6',\n textTertiary: '#959aa0',\n successBg: '#0b130b',\n successFg: '#c5e5c7',\n warningBg: '#19120a',\n warningFg: '#ffe3c1',\n dangerBg: '#170c0b',\n dangerFg: '#f7c9c8',\n infoBg: '#091015',\n infoFg: '#bfd9f1',\n}\n\n/** The Aglyn theme's fields the console's scheme supplies; the rest are Figma's Aglyn mode. */\nexport const THEME_CONSOLE_FIELDS: readonly string[] = [\n 'canvas',\n 'surface',\n 'raised',\n 'hover',\n 'borderSubtle',\n 'textPrimary',\n 'textSecondary',\n 'textTertiary',\n 'successBg',\n 'successFg',\n 'warningBg',\n 'warningFg',\n 'dangerBg',\n 'dangerFg',\n 'infoBg',\n 'infoFg',\n]\n\nconst QUIET_LIGHT: ThemeColors = {\n ...FIGMA_AGLYN_LIGHT,\n canvas: '#f4f4f2',\n surface: '#ffffff',\n raised: '#ffffff',\n sunken: '#ecece9',\n rail: '#eaeae7',\n hover: '#efefec',\n titlebar: '#fafaf8',\n borderSubtle: '#e2e2de',\n borderStrong: '#cbcbc5',\n textPrimary: '#222220',\n textSecondary: '#5f5f5a',\n textTertiary: '#7c7c76',\n}\n\nconst QUIET_DARK: ThemeColors = {\n ...FIGMA_AGLYN_DARK,\n canvas: '#1a1a19',\n surface: '#222221',\n raised: '#2b2b29',\n sunken: '#141413',\n rail: '#121211',\n hover: '#30302e',\n titlebar: '#1e1e1d',\n borderSubtle: '#353533',\n borderStrong: '#4a4a47',\n textPrimary: '#f2f2ef',\n textSecondary: '#b5b5af',\n textTertiary: '#8e8e88',\n}\n\nconst NIGHT: ThemeColors = {\n canvas: '#0b1120',\n surface: '#121a2b',\n raised: '#1a2337',\n sunken: '#070c17',\n rail: '#050913',\n hover: '#1e2840',\n titlebar: '#0e1525',\n borderSubtle: '#222c42',\n borderStrong: '#33405c',\n textPrimary: '#eef2fa',\n textSecondary: '#a9b4cb',\n textTertiary: '#8290ab',\n successBg: '#12301f',\n successFg: '#7bcb8f',\n warningBg: '#352914',\n warningFg: '#f6be6e',\n dangerBg: '#3a1820',\n dangerFg: '#f08a8f',\n infoBg: '#122a47',\n infoFg: '#79b2ee',\n newBg: '#36193f',\n newFg: '#e68af6',\n scrim: '#00000099',\n ...DANGER_DARK,\n}\n\nconst HIGH_CONTRAST_LIGHT: ThemeColors = {\n canvas: '#ffffff',\n surface: '#ffffff',\n raised: '#ffffff',\n sunken: '#f0f0f0',\n rail: '#efefef',\n hover: '#e4e4e4',\n titlebar: '#ffffff',\n borderSubtle: '#6b6b6b',\n borderStrong: '#000000',\n textPrimary: '#000000',\n textSecondary: '#1f1f1f',\n textTertiary: '#3d3d3d',\n successBg: '#ddf3de',\n successFg: '#0b4d10',\n warningBg: '#ffe9c7',\n warningFg: '#5c3a00',\n dangerBg: '#fadbd9',\n dangerFg: '#7a0f0b',\n infoBg: '#ddebfa',\n infoFg: '#0a3a66',\n newBg: '#f1ddf6',\n newFg: '#5e0f70',\n scrim: '#00000080',\n ...DANGER_LIGHT,\n}\n\nconst HIGH_CONTRAST_DARK: ThemeColors = {\n canvas: '#000000',\n surface: '#000000',\n raised: '#0a0a0a',\n sunken: '#141414',\n rail: '#0f0f0f',\n hover: '#262626',\n titlebar: '#000000',\n borderSubtle: '#9a9a9a',\n borderStrong: '#ffffff',\n textPrimary: '#ffffff',\n textSecondary: '#ededed',\n textTertiary: '#c7c7c7',\n successBg: '#0b2a0e',\n successFg: '#9be7a0',\n warningBg: '#2e2000',\n warningFg: '#ffd08a',\n dangerBg: '#330b0a',\n dangerFg: '#ff9e9b',\n infoBg: '#0a2340',\n infoFg: '#9ccbff',\n newBg: '#2e0b38',\n newFg: '#f2a8ff',\n scrim: '#000000b2',\n ...DANGER_DARK,\n}\n\n/** Figma's previews of the two themes it does not hold in full. */\nexport const THEME_PREVIEWS: Readonly<Record<'ocean' | 'warm', ThemePreview>> = {\n ocean: { rail: '#0b3954', surface: '#f2f8fb', canvas: '#e3eef4', accent: '#087e8b' },\n warm: { rail: '#e9dccb', surface: '#fffaf3', canvas: '#f5ece0', accent: '#c2571a' },\n}\n\n/**\n * The rail color a theme's DARK appearance is tinted toward. Ocean's rail is\n * already dark, so it is its own seed; Warm's is a light sand, so its dark\n * appearance leans to a dark sand instead.\n */\nexport const THEME_DARK_RAIL_SEEDS: Readonly<Record<'ocean' | 'warm', string>> = {\n ocean: '#0b3954',\n warm: '#7a5634',\n}\n\n/**\n * A theme's full palette from Figma's four-color preview (AGL-3950), by the\n * same rules for both:\n *\n * Light: the preview's canvas, surface and rail as given; raised = surface;\n * sunken = canvas 25% toward the rail; hover = surface 10% toward the rail;\n * titlebar = surface 35% toward the canvas; the borders are Figma's Aglyn\n * mode's, 18% (subtle) and 22% (strong) toward the rail. Text, status, scrim\n * and the danger fill are Figma's Aglyn mode's.\n *\n * Dark: Figma's Aglyn dark mode tinted toward the dark rail seed: canvas\n * 22%, surface and raised 18%, rail 45%; everything else is that mode's.\n *\n * \"Toward\" is a straight per-channel sRGB blend, rounded to the byte.\n */\nexport function deriveThemeFromPreview(preview: ThemePreview, darkRailSeed: string): {\n light: ThemeColors\n dark: ThemeColors\n} {\n return {\n light: {\n ...FIGMA_AGLYN_LIGHT,\n canvas: preview.canvas,\n surface: preview.surface,\n raised: preview.surface,\n rail: preview.rail,\n sunken: themeLerp(preview.canvas, preview.rail, 0.25),\n hover: themeLerp(preview.surface, preview.rail, 0.1),\n titlebar: themeLerp(preview.surface, preview.canvas, 0.35),\n borderSubtle: themeLerp(FIGMA_AGLYN_LIGHT.borderSubtle, preview.rail, 0.18),\n borderStrong: themeLerp(FIGMA_AGLYN_LIGHT.borderStrong, preview.rail, 0.22),\n },\n dark: {\n ...FIGMA_AGLYN_DARK,\n canvas: themeLerp(FIGMA_AGLYN_DARK.canvas, darkRailSeed, 0.22),\n surface: themeLerp(FIGMA_AGLYN_DARK.surface, darkRailSeed, 0.18),\n raised: themeLerp(FIGMA_AGLYN_DARK.raised, darkRailSeed, 0.18),\n rail: themeLerp(FIGMA_AGLYN_DARK.rail, darkRailSeed, 0.45),\n },\n }\n}\n\nconst OCEAN = deriveThemeFromPreview(THEME_PREVIEWS.ocean, THEME_DARK_RAIL_SEEDS.ocean)\nconst WARM = deriveThemeFromPreview(THEME_PREVIEWS.warm, THEME_DARK_RAIL_SEEDS.warm)\n\n/** The Aglyn blue every Figma theme's \"Accent: Theme\" resolves to. */\nconst AGLYN_ACCENT = '#00b0ff'\n\n/** Every theme, in the order the Theme settings sheet lists them. */\nexport const THEME_PALETTES: readonly ThemePalette[] = [\n { id: 'aglyn', label: '', brandLabel: true, light: AGLYN_LIGHT, dark: AGLYN_DARK, accent: AGLYN_ACCENT, source: 'console' },\n { id: 'quiet', label: 'Quiet', light: QUIET_LIGHT, dark: QUIET_DARK, accent: AGLYN_ACCENT, source: 'figma' },\n { id: 'night', label: 'Night', light: NIGHT, dark: NIGHT, accent: AGLYN_ACCENT, alwaysDark: true, source: 'figma' },\n { id: 'ocean', label: 'Ocean', light: OCEAN.light, dark: OCEAN.dark, accent: THEME_PREVIEWS.ocean.accent, source: 'derived' },\n { id: 'warm', label: 'Warm', light: WARM.light, dark: WARM.dark, accent: THEME_PREVIEWS.warm.accent, source: 'derived' },\n {\n id: 'highContrast',\n label: 'High contrast',\n light: HIGH_CONTRAST_LIGHT,\n dark: HIGH_CONTRAST_DARK,\n accent: AGLYN_ACCENT,\n source: 'figma',\n },\n]\n\n// ── Accents ─────────────────────────────────────────────────────────────────\n\n/** The surfaces an accent's text is held to. */\nconst LIGHT_PAGE = AGLYN_LIGHT.surface\nconst DARK_PAGE = AGLYN_DARK.surface\n\n/** Text and icons on a fill that keeps a light brand color. */\nconst DARK_ON_LIGHTNESS = 0.08\n\nfunction accentText(raw: string): Pick<ThemeAccent, 'textLight' | 'textDark'> {\n return {\n textLight: walkToContrast(raw, LIGHT_PAGE, THEME_TEXT_CONTRAST, -1),\n textDark: walkToContrast(raw, DARK_PAGE, THEME_TEXT_CONTRAST, 1),\n }\n}\n\n/** Figma's Accent modes, exactly as the Accent collection holds them. */\nconst FIGMA_ACCENTS: ReadonlyArray<{ id: string; label: string; accent: Omit<ThemeAccent, 'textLight' | 'textDark'> }> = [\n {\n id: 'blue',\n label: 'Blue',\n accent: {\n fill: '#0073ae',\n icon: '#0091d5',\n soft: '#00b0ff29',\n ring: '#00b0ff',\n on: '#ffffff',\n hover: '#00669b',\n pressed: '#005884',\n },\n },\n {\n id: 'violet',\n label: 'Violet',\n accent: {\n fill: '#5b3fd6',\n icon: '#8c72f5',\n soft: '#7a5cf02e',\n ring: '#7a5cf0',\n on: '#ffffff',\n hover: '#4e34c2',\n pressed: '#4129aa',\n },\n },\n {\n id: 'pink',\n label: 'Pink',\n accent: {\n fill: '#e040fb',\n icon: '#c93ae3',\n soft: '#e040fb29',\n ring: '#e040fb',\n on: '#1e0424',\n hover: '#cc2ee6',\n pressed: '#b523cf',\n },\n },\n {\n id: 'workspaceBrand',\n label: 'Workspace brand',\n accent: {\n fill: '#2e7d5b',\n icon: '#2e8f66',\n soft: '#2e7d5b2e',\n ring: '#3fa77a',\n on: '#ffffff',\n hover: '#276c4e',\n pressed: '#205a41',\n },\n },\n]\n\n/** The named accents, Figma's own values with their text shades. */\nexport const THEME_ACCENTS: readonly ThemeNamedAccent[] = FIGMA_ACCENTS.map(({ id, label, accent }) => ({\n id,\n label,\n accent: { ...accent, ...accentText(accent.ring) },\n}))\n\n/** The accent picker's swatches, in Figma's order. */\nexport const THEME_ACCENT_SWATCHES: readonly ThemeAccentSwatch[] = [\n { id: 'aglynBlue', label: 'Brand blue', color: '#0073ae' },\n { id: 'sky', label: 'Sky', color: '#00b0ff' },\n { id: 'violet', label: 'Violet', color: '#7a5cf0' },\n { id: 'pink', label: 'Pink', color: '#e040fb' },\n { id: 'green', label: 'Green', color: '#2e7d5b' },\n { id: 'warm', label: 'Warm', color: '#c2571a' },\n]\n\n/**\n * Every control's shade of one accent color (AGL-3950).\n *\n * A color one of Figma's Accent modes is drawn from (its ring) answers with\n * that mode exactly: Figma wins where it defines a value. Any other color is\n * derived:\n *\n * 1. White text on the color already reads (4.5:1): the fill is the color,\n * `on` is white.\n * 2. The color carries white text only at the large-text bar (3:1 up to\n * 4.5:1) and near-black text reads on it at 4.5:1: the fill KEEPS the\n * brand color and `on` flips to near-black, the color's own hue at 8%\n * lightness. Darkening such a color far enough for white text moves it\n * visibly off the brand; this is Figma's Pink (#E040FB, 3.34:1). The 3:1\n * threshold is the one that sends Pink here and Blue (2.43:1) and the\n * workspace green (2.99:1) to rule 3, as Figma draws them.\n * 3. Otherwise the fill is the color darkened in 2% HSL-lightness steps until\n * white reaches 4.5:1, and `on` is white.\n *\n * hover and pressed are the fill 4% and 8% darker (HSL lightness), soft is\n * the color at 16% alpha, ring is the color, icon is the color darkened in 2%\n * steps until it reaches 3:1 on the light surface, and the two text shades\n * walk it (darker on light, lighter on dark) until 4.5:1 on that surface.\n * Null for anything that is not `#RRGGBB`.\n */\nexport function deriveAccent(color: string): ThemeAccent | null {\n if (!isThemeColor(color)) return null\n const raw = color.toLowerCase()\n const named = THEME_ACCENTS.find((entry) => entry.accent.ring === raw)\n if (named) return { ...named.accent }\n const white = '#ffffff'\n const onWhite = themeContrast(raw, white)\n let fill = raw\n let on = white\n if (onWhite < THEME_TEXT_CONTRAST) {\n const rgb = channels(raw) as [number, number, number]\n const [h, s] = toHsl(rgb)\n const nearBlack = fromHsl(h, Math.min(s, 0.8), DARK_ON_LIGHTNESS)\n if (onWhite >= THEME_NON_TEXT_CONTRAST && themeContrast(raw, nearBlack) >= THEME_TEXT_CONTRAST) {\n on = nearBlack\n } else {\n fill = walkToContrast(raw, white, THEME_TEXT_CONTRAST, -1)\n }\n }\n return {\n fill,\n icon: walkToContrast(raw, LIGHT_PAGE, THEME_NON_TEXT_CONTRAST, -1),\n soft: withAlpha(raw, 0.16),\n ring: raw,\n on,\n hover: shiftLightness(fill, -0.04),\n pressed: shiftLightness(fill, -0.08),\n ...accentText(raw),\n }\n}\n\n// ── Density ─────────────────────────────────────────────────────────────────\n\n/** Figma's Density modes. */\nexport const THEME_DENSITIES: readonly ThemeDensity[] = [\n {\n id: 'comfortable',\n label: 'Comfortable',\n space2: 2,\n space4: 4,\n space6: 6,\n space8: 8,\n space10: 10,\n space12: 12,\n space16: 16,\n space20: 20,\n space24: 24,\n space32: 32,\n space40: 40,\n space48: 48,\n space64: 64,\n radiusXs: 4,\n radiusSm: 6,\n radiusMd: 8,\n radiusLg: 12,\n radiusXl: 16,\n radius2xl: 24,\n radiusFull: 999,\n rowHeight: 32,\n controlHeight: 32,\n listRowHeight: 60,\n phoneRowHeight: 52,\n titlebarHeight: 52,\n },\n {\n id: 'compact',\n label: 'Compact',\n space2: 2,\n space4: 4,\n space6: 4,\n space8: 6,\n space10: 8,\n space12: 8,\n space16: 12,\n space20: 16,\n space24: 20,\n space32: 24,\n space40: 32,\n space48: 40,\n space64: 48,\n radiusXs: 4,\n radiusSm: 6,\n radiusMd: 8,\n radiusLg: 12,\n radiusXl: 16,\n radius2xl: 24,\n radiusFull: 999,\n rowHeight: 28,\n controlHeight: 28,\n listRowHeight: 48,\n phoneRowHeight: 44,\n titlebarHeight: 44,\n },\n]\n\n/** A theme by id, Aglyn for anything unknown. */\nexport function themePalette(id: unknown): ThemePalette {\n return THEME_PALETTES.find((entry) => entry.id === id) ?? THEME_PALETTES[0]\n}\n"],"names":["HEX","channels","color","match","exec","value","map","index","parseInt","slice","hexOf","rgb","Math","round","min","max","toString","padStart","join","isThemeColor","test","linear","channel","luminance","themeContrast","a","b","x","y","high","low","sort","p","q","toHsl","r","g","red","green","blue","lightness","delta","saturation","hue","fromHsl","l","t","shiftLightness","h","s","walkToContrast","against","minContrast","direction","candidate","step","themeLerp","from","to","amount","withAlpha","alpha","THEME_TEXT_CONTRAST","THEME_NON_TEXT_CONTRAST","DANGER_LIGHT","dangerFill","dangerFillHover","dangerFillPressed","DANGER_DARK","FIGMA_AGLYN_LIGHT","canvas","surface","raised","sunken","rail","hover","titlebar","borderSubtle","borderStrong","textPrimary","textSecondary","textTertiary","successBg","successFg","warningBg","warningFg","dangerBg","dangerFg","infoBg","infoFg","newBg","newFg","scrim","FIGMA_AGLYN_DARK","THEME_FIGMA_BASE","light","dark","AGLYN_LIGHT","AGLYN_DARK","THEME_CONSOLE_FIELDS","QUIET_LIGHT","QUIET_DARK","NIGHT","HIGH_CONTRAST_LIGHT","HIGH_CONTRAST_DARK","THEME_PREVIEWS","ocean","accent","warm","THEME_DARK_RAIL_SEEDS","deriveThemeFromPreview","preview","darkRailSeed","OCEAN","WARM","AGLYN_ACCENT","THEME_PALETTES","id","label","brandLabel","source","alwaysDark","LIGHT_PAGE","DARK_PAGE","DARK_ON_LIGHTNESS","accentText","raw","textLight","textDark","FIGMA_ACCENTS","fill","icon","soft","ring","on","pressed","THEME_ACCENTS","THEME_ACCENT_SWATCHES","deriveAccent","toLowerCase","named","find","entry","white","onWhite","nearBlack","THEME_DENSITIES","space2","space4","space6","space8","space10","space12","space16","space20","space24","space32","space40","space48","space64","radiusXs","radiusSm","radiusMd","radiusLg","radiusXl","radius2xl","radiusFull","rowHeight","controlHeight","listRowHeight","phoneRowHeight","titlebarHeight","themePalette"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;;;;;;;CAyBC,GAED,4BAA4B;AAmI5B,+EAA+E;AAE/E,MAAMA,MAAM;AAEZ,0EAA0E,GAC1E,SAASC,SAASC,KAAa;IAC7B,MAAMC,QAAQH,IAAII,IAAI,CAACF,gBAAAA,QAAS;IAChC,IAAI,CAACC,OAAO,OAAO;IACnB,MAAME,QAAQF,KAAK,CAAC,EAAE;IACtB,OAAO;QAAC;QAAG;QAAG;KAAE,CAACG,GAAG,CAAC,CAACC,QAAUC,SAASH,MAAMI,KAAK,CAACF,OAAOA,QAAQ,IAAI;AAC1E;AAEA,SAASG,MAAMC,GAAsB;IACnC,OAAO,CAAC,CAAC,EAAEA,IACRL,GAAG,CAAC,CAACD,QAAUO,KAAKC,KAAK,CAACD,KAAKE,GAAG,CAAC,KAAKF,KAAKG,GAAG,CAAC,GAAGV,SAASW,QAAQ,CAAC,IAAIC,QAAQ,CAAC,GAAG,MACtFC,IAAI,CAAC,KAAK;AACf;AAEA,kCAAkC,GAClC,OAAO,SAASC,aAAad,KAAc;IACzC,OAAO,OAAOA,UAAU,YAAY,kBAAkBe,IAAI,CAACf;AAC7D;AAEA,SAASgB,OAAOC,OAAe;IAC7B,MAAMjB,QAAQiB,UAAU;IACxB,OAAOjB,SAAS,UAAUA,QAAQ,QAAQ,AAAC,CAAA,AAACA,CAAAA,QAAQ,KAAI,IAAK,KAAI,KAAM;AACzE;AAEA,SAASkB,UAAUZ,GAAsB;IACvC,OAAO,SAASU,OAAOV,GAAG,CAAC,EAAE,IAAI,SAASU,OAAOV,GAAG,CAAC,EAAE,IAAI,SAASU,OAAOV,GAAG,CAAC,EAAE;AACnF;AAEA,gGAAgG,GAChG,OAAO,SAASa,cAAcC,CAAS,EAAEC,CAAS;IAChD,MAAMC,IAAI1B,SAASwB;IACnB,MAAMG,IAAI3B,SAASyB;IACnB,IAAI,CAACC,KAAK,CAACC,GAAG,OAAO;IACrB,MAAM,CAACC,MAAMC,IAAI,GAAG;QAACP,UAAUI;QAAIJ,UAAUK;KAAG,CAACG,IAAI,CAAC,CAACC,GAAGC,IAAMA,IAAID;IACpE,OAAO,AAACH,CAAAA,OAAO,IAAG,IAAMC,CAAAA,MAAM,IAAG;AACnC;AAEA,SAASI,MAAM,CAACC,GAAGC,GAAGV,EAAqB;IACzC,MAAM,CAACW,KAAKC,OAAOC,KAAK,GAAG;QAACJ,IAAI;QAAKC,IAAI;QAAKV,IAAI;KAAI;IACtD,MAAMX,MAAMH,KAAKG,GAAG,CAACsB,KAAKC,OAAOC;IACjC,MAAMzB,MAAMF,KAAKE,GAAG,CAACuB,KAAKC,OAAOC;IACjC,MAAMC,YAAY,AAACzB,CAAAA,MAAMD,GAAE,IAAK;IAChC,IAAIC,QAAQD,KAAK,OAAO;QAAC;QAAG;QAAG0B;KAAU;IACzC,MAAMC,QAAQ1B,MAAMD;IACpB,MAAM4B,aAAaF,YAAY,MAAMC,QAAS,CAAA,IAAI1B,MAAMD,GAAE,IAAK2B,QAAS1B,CAAAA,MAAMD,GAAE;IAChF,IAAI6B;IACJ,IAAI5B,QAAQsB,KAAKM,MAAM,AAACL,CAAAA,QAAQC,IAAG,IAAKE,QAASH,CAAAA,QAAQC,OAAO,IAAI,CAAA;SAC/D,IAAIxB,QAAQuB,OAAOK,MAAM,AAACJ,CAAAA,OAAOF,GAAE,IAAKI,QAAQ;SAChDE,MAAM,AAACN,CAAAA,MAAMC,KAAI,IAAKG,QAAQ;IACnC,OAAO;QAACE,MAAM;QAAGD;QAAYF;KAAU;AACzC;AAEA,SAASI,QAAQD,GAAW,EAAED,UAAkB,EAAEF,SAAiB;IACjE,MAAMK,IAAIjC,KAAKE,GAAG,CAAC,GAAGF,KAAKG,GAAG,CAAC,GAAGyB;IAClC,IAAIE,eAAe,GAAG,OAAOhC,MAAM;QAACmC,IAAI;QAAKA,IAAI;QAAKA,IAAI;KAAI;IAC9D,MAAMZ,IAAIY,IAAI,MAAMA,IAAK,CAAA,IAAIH,UAAS,IAAKG,IAAIH,aAAaG,IAAIH;IAChE,MAAMV,IAAI,IAAIa,IAAIZ;IAClB,MAAMX,UAAU,CAACwB;QACf,IAAInB,IAAImB;QACR,IAAInB,IAAI,GAAGA,KAAK;QAChB,IAAIA,IAAI,GAAGA,KAAK;QAChB,IAAIA,IAAI,IAAI,GAAG,OAAOK,IAAI,AAACC,CAAAA,IAAID,CAAAA,IAAK,IAAIL;QACxC,IAAIA,IAAI,IAAI,GAAG,OAAOM;QACtB,IAAIN,IAAI,IAAI,GAAG,OAAOK,IAAI,AAACC,CAAAA,IAAID,CAAAA,IAAM,CAAA,IAAI,IAAIL,CAAAA,IAAK;QAClD,OAAOK;IACT;IACA,OAAOtB,MAAM;QAACY,QAAQqB,MAAM,IAAI,KAAK;QAAKrB,QAAQqB,OAAO;QAAKrB,QAAQqB,MAAM,IAAI,KAAK;KAAI;AAC3F;AAEA,+DAA+D,GAC/D,SAASI,eAAe7C,KAAa,EAAEuC,KAAa;IAClD,MAAM9B,MAAMV,SAASC;IACrB,IAAI,CAACS,KAAK,OAAOT;IACjB,MAAM,CAAC8C,GAAGC,GAAGJ,EAAE,GAAGX,MAAMvB;IACxB,OAAOiC,QAAQI,GAAGC,GAAGJ,IAAIJ;AAC3B;AAEA;;;CAGC,GACD,SAASS,eAAehD,KAAa,EAAEiD,OAAe,EAAEC,WAAmB,EAAEC,SAAiB;IAC5F,IAAIC,YAAYpD;IAChB,IAAK,IAAIqD,OAAO,GAAGA,QAAQ,IAAIA,QAAQ,EAAG;QACxCD,YAAYP,eAAe7C,OAAOmD,YAAY,OAAOE;QACrD,IAAI/B,cAAc8B,WAAWH,YAAYC,aAAa,OAAOE;IAC/D;IACA,OAAOA;AACT;AAEA,sEAAsE,GACtE,OAAO,SAASE,UAAUC,IAAY,EAAEC,EAAU,EAAEC,MAAc;IAChE,MAAMlC,IAAIxB,SAASwD;IACnB,MAAM/B,IAAIzB,SAASyD;IACnB,IAAI,CAACjC,KAAK,CAACC,GAAG,OAAO+B;IACrB,OAAO/C,MAAMe,EAAEnB,GAAG,CAAC,CAACD,OAAOE,QAAUF,QAAQ,AAACqB,CAAAA,CAAC,CAACnB,MAAM,GAAGF,KAAI,IAAKsD;AACpE;AAEA,6CAA6C,GAC7C,SAASC,UAAU1D,KAAa,EAAE2D,KAAa;IAC7C,MAAMlD,MAAMV,SAASC;IACrB,IAAI,CAACS,KAAK,OAAOT;IACjB,OAAO,GAAGQ,MAAMC,OAAOC,KAAKC,KAAK,CAACgD,QAAQ,KAAK7C,QAAQ,CAAC,IAAIC,QAAQ,CAAC,GAAG,MAAM;AAChF;AAEA,+EAA+E;AAE/E,mDAAmD,GACnD,OAAO,MAAM6C,sBAAsB,IAAG;AAEtC,6DAA6D,GAC7D,OAAO,MAAMC,0BAA0B,EAAC;AAExC,MAAMC,eAAe;IAAEC,YAAY;IAAWC,iBAAiB;IAAWC,mBAAmB;AAAU;AACvG,MAAMC,cAAc;IAAEH,YAAY;IAAWC,iBAAiB;IAAWC,mBAAmB;AAAU;AAEtG;;;;;CAKC,GACD,MAAME,oBAAiC;IACrCC,QAAQ;IACRC,SAAS;IACTC,QAAQ;IACRC,QAAQ;IACRC,MAAM;IACNC,OAAO;IACPC,UAAU;IACVC,cAAc;IACdC,cAAc;IACdC,aAAa;IACbC,eAAe;IACfC,cAAc;IACdC,WAAW;IACXC,WAAW;IACXC,WAAW;IACXC,WAAW;IACXC,UAAU;IACVC,UAAU;IACVC,QAAQ;IACRC,QAAQ;IACRC,OAAO;IACPC,OAAO;IACPC,OAAO;GACJ5B;AAGL,MAAM6B,mBAAgC;IACpCvB,QAAQ;IACRC,SAAS;IACTC,QAAQ;IACRC,QAAQ;IACRC,MAAM;IACNC,OAAO;IACPC,UAAU;IACVC,cAAc;IACdC,cAAc;IACdC,aAAa;IACbC,eAAe;IACfC,cAAc;IACdC,WAAW;IACXC,WAAW;IACXC,WAAW;IACXC,WAAW;IACXC,UAAU;IACVC,UAAU;IACVC,QAAQ;IACRC,QAAQ;IACRC,OAAO;IACPC,OAAO;IACPC,OAAO;GACJxB;AASL;;;;CAIC,GACD,OAAO,MAAM0B,mBAAoC;IAAEC,OAAO1B;IAAmB2B,MAAMH;AAAiB,EAAC;AAErG;;;;;;;;;;;;CAYC,GACD,MAAMI,cAA2B,aAC5B5B;IACHC,QAAQ;IACRC,SAAS;IACTC,QAAQ;IACRG,OAAO;IACPE,cAAc;IACdE,aAAa;IACbC,eAAe;IACfC,cAAc;IACdC,WAAW;IACXC,WAAW;IACXC,WAAW;IACXC,WAAW;IACXC,UAAU;IACVC,UAAU;IACVC,QAAQ;IACRC,QAAQ;;AAGV,MAAMS,aAA0B,aAC3BL;IACHvB,QAAQ;IACRC,SAAS;IACTC,QAAQ;IACRG,OAAO;IACPE,cAAc;IACdE,aAAa;IACbC,eAAe;IACfC,cAAc;IACdC,WAAW;IACXC,WAAW;IACXC,WAAW;IACXC,WAAW;IACXC,UAAU;IACVC,UAAU;IACVC,QAAQ;IACRC,QAAQ;;AAGV,6FAA6F,GAC7F,OAAO,MAAMU,uBAA0C;IACrD;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;CACD,CAAA;AAED,MAAMC,cAA2B,aAC5B/B;IACHC,QAAQ;IACRC,SAAS;IACTC,QAAQ;IACRC,QAAQ;IACRC,MAAM;IACNC,OAAO;IACPC,UAAU;IACVC,cAAc;IACdC,cAAc;IACdC,aAAa;IACbC,eAAe;IACfC,cAAc;;AAGhB,MAAMoB,aAA0B,aAC3BR;IACHvB,QAAQ;IACRC,SAAS;IACTC,QAAQ;IACRC,QAAQ;IACRC,MAAM;IACNC,OAAO;IACPC,UAAU;IACVC,cAAc;IACdC,cAAc;IACdC,aAAa;IACbC,eAAe;IACfC,cAAc;;AAGhB,MAAMqB,QAAqB;IACzBhC,QAAQ;IACRC,SAAS;IACTC,QAAQ;IACRC,QAAQ;IACRC,MAAM;IACNC,OAAO;IACPC,UAAU;IACVC,cAAc;IACdC,cAAc;IACdC,aAAa;IACbC,eAAe;IACfC,cAAc;IACdC,WAAW;IACXC,WAAW;IACXC,WAAW;IACXC,WAAW;IACXC,UAAU;IACVC,UAAU;IACVC,QAAQ;IACRC,QAAQ;IACRC,OAAO;IACPC,OAAO;IACPC,OAAO;GACJxB;AAGL,MAAMmC,sBAAmC;IACvCjC,QAAQ;IACRC,SAAS;IACTC,QAAQ;IACRC,QAAQ;IACRC,MAAM;IACNC,OAAO;IACPC,UAAU;IACVC,cAAc;IACdC,cAAc;IACdC,aAAa;IACbC,eAAe;IACfC,cAAc;IACdC,WAAW;IACXC,WAAW;IACXC,WAAW;IACXC,WAAW;IACXC,UAAU;IACVC,UAAU;IACVC,QAAQ;IACRC,QAAQ;IACRC,OAAO;IACPC,OAAO;IACPC,OAAO;GACJ5B;AAGL,MAAMwC,qBAAkC;IACtClC,QAAQ;IACRC,SAAS;IACTC,QAAQ;IACRC,QAAQ;IACRC,MAAM;IACNC,OAAO;IACPC,UAAU;IACVC,cAAc;IACdC,cAAc;IACdC,aAAa;IACbC,eAAe;IACfC,cAAc;IACdC,WAAW;IACXC,WAAW;IACXC,WAAW;IACXC,WAAW;IACXC,UAAU;IACVC,UAAU;IACVC,QAAQ;IACRC,QAAQ;IACRC,OAAO;IACPC,OAAO;IACPC,OAAO;GACJxB;AAGL,iEAAiE,GACjE,OAAO,MAAMqC,iBAAmE;IAC9EC,OAAO;QAAEhC,MAAM;QAAWH,SAAS;QAAWD,QAAQ;QAAWqC,QAAQ;IAAU;IACnFC,MAAM;QAAElC,MAAM;QAAWH,SAAS;QAAWD,QAAQ;QAAWqC,QAAQ;IAAU;AACpF,EAAC;AAED;;;;CAIC,GACD,OAAO,MAAME,wBAAoE;IAC/EH,OAAO;IACPE,MAAM;AACR,EAAC;AAED;;;;;;;;;;;;;;CAcC,GACD,OAAO,SAASE,uBAAuBC,OAAqB,EAAEC,YAAoB;IAIhF,OAAO;QACLjB,OAAO,aACF1B;YACHC,QAAQyC,QAAQzC,MAAM;YACtBC,SAASwC,QAAQxC,OAAO;YACxBC,QAAQuC,QAAQxC,OAAO;YACvBG,MAAMqC,QAAQrC,IAAI;YAClBD,QAAQjB,UAAUuD,QAAQzC,MAAM,EAAEyC,QAAQrC,IAAI,EAAE;YAChDC,OAAOnB,UAAUuD,QAAQxC,OAAO,EAAEwC,QAAQrC,IAAI,EAAE;YAChDE,UAAUpB,UAAUuD,QAAQxC,OAAO,EAAEwC,QAAQzC,MAAM,EAAE;YACrDO,cAAcrB,UAAUa,kBAAkBQ,YAAY,EAAEkC,QAAQrC,IAAI,EAAE;YACtEI,cAActB,UAAUa,kBAAkBS,YAAY,EAAEiC,QAAQrC,IAAI,EAAE;;QAExEsB,MAAM,aACDH;YACHvB,QAAQd,UAAUqC,iBAAiBvB,MAAM,EAAE0C,cAAc;YACzDzC,SAASf,UAAUqC,iBAAiBtB,OAAO,EAAEyC,cAAc;YAC3DxC,QAAQhB,UAAUqC,iBAAiBrB,MAAM,EAAEwC,cAAc;YACzDtC,MAAMlB,UAAUqC,iBAAiBnB,IAAI,EAAEsC,cAAc;;IAEzD;AACF;AAEA,MAAMC,QAAQH,uBAAuBL,eAAeC,KAAK,EAAEG,sBAAsBH,KAAK;AACtF,MAAMQ,OAAOJ,uBAAuBL,eAAeG,IAAI,EAAEC,sBAAsBD,IAAI;AAEnF,oEAAoE,GACpE,MAAMO,eAAe;AAErB,mEAAmE,GACnE,OAAO,MAAMC,iBAA0C;IACrD;QAAEC,IAAI;QAASC,OAAO;QAAIC,YAAY;QAAMxB,OAAOE;QAAaD,MAAME;QAAYS,QAAQQ;QAAcK,QAAQ;IAAU;IAC1H;QAAEH,IAAI;QAASC,OAAO;QAASvB,OAAOK;QAAaJ,MAAMK;QAAYM,QAAQQ;QAAcK,QAAQ;IAAQ;IAC3G;QAAEH,IAAI;QAASC,OAAO;QAASvB,OAAOO;QAAON,MAAMM;QAAOK,QAAQQ;QAAcM,YAAY;QAAMD,QAAQ;IAAQ;IAClH;QAAEH,IAAI;QAASC,OAAO;QAASvB,OAAOkB,MAAMlB,KAAK;QAAEC,MAAMiB,MAAMjB,IAAI;QAAEW,QAAQF,eAAeC,KAAK,CAACC,MAAM;QAAEa,QAAQ;IAAU;IAC5H;QAAEH,IAAI;QAAQC,OAAO;QAAQvB,OAAOmB,KAAKnB,KAAK;QAAEC,MAAMkB,KAAKlB,IAAI;QAAEW,QAAQF,eAAeG,IAAI,CAACD,MAAM;QAAEa,QAAQ;IAAU;IACvH;QACEH,IAAI;QACJC,OAAO;QACPvB,OAAOQ;QACPP,MAAMQ;QACNG,QAAQQ;QACRK,QAAQ;IACV;CACD,CAAA;AAED,+EAA+E;AAE/E,8CAA8C,GAC9C,MAAME,aAAazB,YAAY1B,OAAO;AACtC,MAAMoD,YAAYzB,WAAW3B,OAAO;AAEpC,6DAA6D,GAC7D,MAAMqD,oBAAoB;AAE1B,SAASC,WAAWC,GAAW;IAC7B,OAAO;QACLC,WAAW7E,eAAe4E,KAAKJ,YAAY5D,qBAAqB,CAAC;QACjEkE,UAAU9E,eAAe4E,KAAKH,WAAW7D,qBAAqB;IAChE;AACF;AAEA,uEAAuE,GACvE,MAAMmE,gBAAmH;IACvH;QACEZ,IAAI;QACJC,OAAO;QACPX,QAAQ;YACNuB,MAAM;YACNC,MAAM;YACNC,MAAM;YACNC,MAAM;YACNC,IAAI;YACJ3D,OAAO;YACP4D,SAAS;QACX;IACF;IACA;QACElB,IAAI;QACJC,OAAO;QACPX,QAAQ;YACNuB,MAAM;YACNC,MAAM;YACNC,MAAM;YACNC,MAAM;YACNC,IAAI;YACJ3D,OAAO;YACP4D,SAAS;QACX;IACF;IACA;QACElB,IAAI;QACJC,OAAO;QACPX,QAAQ;YACNuB,MAAM;YACNC,MAAM;YACNC,MAAM;YACNC,MAAM;YACNC,IAAI;YACJ3D,OAAO;YACP4D,SAAS;QACX;IACF;IACA;QACElB,IAAI;QACJC,OAAO;QACPX,QAAQ;YACNuB,MAAM;YACNC,MAAM;YACNC,MAAM;YACNC,MAAM;YACNC,IAAI;YACJ3D,OAAO;YACP4D,SAAS;QACX;IACF;CACD;AAED,kEAAkE,GAClE,OAAO,MAAMC,gBAA6CP,cAAc3H,GAAG,CAAC,CAAC,EAAE+G,EAAE,EAAEC,KAAK,EAAEX,MAAM,EAAE,GAAM,CAAA;QACtGU;QACAC;QACAX,QAAQ,aAAKA,QAAWkB,WAAWlB,OAAO0B,IAAI;IAChD,CAAA,GAAG;AAEH,oDAAoD,GACpD,OAAO,MAAMI,wBAAsD;IACjE;QAAEpB,IAAI;QAAaC,OAAO;QAAcpH,OAAO;IAAU;IACzD;QAAEmH,IAAI;QAAOC,OAAO;QAAOpH,OAAO;IAAU;IAC5C;QAAEmH,IAAI;QAAUC,OAAO;QAAUpH,OAAO;IAAU;IAClD;QAAEmH,IAAI;QAAQC,OAAO;QAAQpH,OAAO;IAAU;IAC9C;QAAEmH,IAAI;QAASC,OAAO;QAASpH,OAAO;IAAU;IAChD;QAAEmH,IAAI;QAAQC,OAAO;QAAQpH,OAAO;IAAU;CAC/C,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;CAwBC,GACD,OAAO,SAASwI,aAAaxI,KAAa;IACxC,IAAI,CAACiB,aAAajB,QAAQ,OAAO;IACjC,MAAM4H,MAAM5H,MAAMyI,WAAW;IAC7B,MAAMC,QAAQJ,cAAcK,IAAI,CAAC,CAACC,QAAUA,MAAMnC,MAAM,CAAC0B,IAAI,KAAKP;IAClE,IAAIc,OAAO,OAAO,aAAKA,MAAMjC,MAAM;IACnC,MAAMoC,QAAQ;IACd,MAAMC,UAAUxH,cAAcsG,KAAKiB;IACnC,IAAIb,OAAOJ;IACX,IAAIQ,KAAKS;IACT,IAAIC,UAAUlF,qBAAqB;QACjC,MAAMnD,MAAMV,SAAS6H;QACrB,MAAM,CAAC9E,GAAGC,EAAE,GAAGf,MAAMvB;QACrB,MAAMsI,YAAYrG,QAAQI,GAAGpC,KAAKE,GAAG,CAACmC,GAAG,MAAM2E;QAC/C,IAAIoB,WAAWjF,2BAA2BvC,cAAcsG,KAAKmB,cAAcnF,qBAAqB;YAC9FwE,KAAKW;QACP,OAAO;YACLf,OAAOhF,eAAe4E,KAAKiB,OAAOjF,qBAAqB,CAAC;QAC1D;IACF;IACA,OAAO;QACLoE;QACAC,MAAMjF,eAAe4E,KAAKJ,YAAY3D,yBAAyB,CAAC;QAChEqE,MAAMxE,UAAUkE,KAAK;QACrBO,MAAMP;QACNQ;QACA3D,OAAO5B,eAAemF,MAAM,CAAC;QAC7BK,SAASxF,eAAemF,MAAM,CAAC;OAC5BL,WAAWC;AAElB;AAEA,+EAA+E;AAE/E,2BAA2B,GAC3B,OAAO,MAAMoB,kBAA2C;IACtD;QACE7B,IAAI;QACJC,OAAO;QACP6B,QAAQ;QACRC,QAAQ;QACRC,QAAQ;QACRC,QAAQ;QACRC,SAAS;QACTC,SAAS;QACTC,SAAS;QACTC,SAAS;QACTC,SAAS;QACTC,SAAS;QACTC,SAAS;QACTC,SAAS;QACTC,SAAS;QACTC,UAAU;QACVC,UAAU;QACVC,UAAU;QACVC,UAAU;QACVC,UAAU;QACVC,WAAW;QACXC,YAAY;QACZC,WAAW;QACXC,eAAe;QACfC,eAAe;QACfC,gBAAgB;QAChBC,gBAAgB;IAClB;IACA;QACEtD,IAAI;QACJC,OAAO;QACP6B,QAAQ;QACRC,QAAQ;QACRC,QAAQ;QACRC,QAAQ;QACRC,SAAS;QACTC,SAAS;QACTC,SAAS;QACTC,SAAS;QACTC,SAAS;QACTC,SAAS;QACTC,SAAS;QACTC,SAAS;QACTC,SAAS;QACTC,UAAU;QACVC,UAAU;QACVC,UAAU;QACVC,UAAU;QACVC,UAAU;QACVC,WAAW;QACXC,YAAY;QACZC,WAAW;QACXC,eAAe;QACfC,eAAe;QACfC,gBAAgB;QAChBC,gBAAgB;IAClB;CACD,CAAA;AAED,+CAA+C,GAC/C,OAAO,SAASC,aAAavD,EAAW;QAC/BD;IAAP,QAAOA,uBAAAA,eAAeyB,IAAI,CAAC,CAACC,QAAUA,MAAMzB,EAAE,KAAKA,eAA5CD,uBAAmDA,cAAc,CAAC,EAAE;AAC7E"}
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/native-themes.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * THE NATIVE APPS' THEMES, AS DATA (AGL-3950).\n *\n * The values are read from the Native App Figma file's variables (Themes,\n * page 2:8; collections Aglyn / Theme, Aglyn / Appearance, Aglyn / Accent,\n * Aglyn / Brand and Aglyn / Density), which win wherever they define a value:\n *\n * - `THEME_PALETTES`: Aglyn is the look that ships, the console's resolved\n * MUI scheme (see `AGLYN_LIGHT`); Quiet, Night and High contrast, light and\n * dark, are exactly as Figma's Theme modes hold them. Ocean and Warm exist in Figma\n * only as four-color previews (rail, surface, canvas, accent), so their\n * full palettes are derived here by `deriveThemeFromPreview`, the rule\n * below, and drawn back into Figma as swatch boards.\n * - `THEME_ACCENTS`: the Accent modes Blue, Violet, Pink and Workspace\n * brand, exactly. `deriveAccent` returns them for their ring colors and\n * derives any other color (a picked accent, a workspace's brand) by rule.\n * - `THEME_ACCENT_SWATCHES`: the five swatches the Theme settings sheet shows.\n * - `THEME_RETIRED_ACCENTS`: retired blues, and the brand blue each reads as.\n * - `THEME_DENSITIES`: Comfortable and Compact.\n *\n * Contrast is WCAG 2's relative luminance, as `accessible-shade.ts` (the\n * console's theme) measures it; lightness steps are HSL lightness, as it\n * walks them.\n *\n * NO IMPORTS, on purpose: the native apps read the values, the types and the\n * answers of these functions through the generated contracts.\n */\n\n/** A theme the apps offer. */\nexport type ThemePaletteId = 'aglyn' | 'quiet' | 'night' | 'ocean' | 'warm' | 'highContrast'\n\n/** One appearance of a theme: every color a native screen draws from. */\nexport interface ThemeColors {\n canvas: string\n surface: string\n raised: string\n sunken: string\n rail: string\n hover: string\n titlebar: string\n borderSubtle: string\n borderStrong: string\n textPrimary: string\n textSecondary: string\n textTertiary: string\n successBg: string\n successFg: string\n warningBg: string\n warningFg: string\n dangerBg: string\n dangerFg: string\n infoBg: string\n infoFg: string\n newBg: string\n newFg: string\n /** `#RRGGBBAA`: the wash behind a sheet. */\n scrim: string\n /** A destructive button's fill, and its hover and pressed fills. */\n dangerFill: string\n dangerFillHover: string\n dangerFillPressed: string\n}\n\n/**\n * Where a theme's values come from: the console's resolved MUI scheme (the\n * look that ships, Aglyn), Figma's variables, or derived from a Figma preview.\n */\nexport type ThemeSource = 'console' | 'figma' | 'derived'\n\nexport interface ThemePalette {\n id: ThemePaletteId\n /** The name a picker shows; empty when `brandLabel` says to show the configured brand name. */\n label: string\n /** The theme is named after the product: show the build's configured brand name (white-label safe). */\n brandLabel?: boolean\n light: ThemeColors\n dark: ThemeColors\n /** The theme's own accent (\"Accent: Theme\"), as a raw color for `deriveAccent`. */\n accent: string\n /** Night is the same in both appearances: the theme is always dark. */\n alwaysDark?: boolean\n source: ThemeSource\n}\n\n/** An accent, as every control that paints it reads it. */\nexport interface ThemeAccent {\n /** A filled control's background. */\n fill: string\n /** An icon or an indicator in the accent, on the light surfaces. */\n icon: string\n /** `#RRGGBBAA`: a selected row's or a chip's wash. */\n soft: string\n /** The focus ring: the raw accent. */\n ring: string\n /** Text and icons ON `fill`. */\n on: string\n hover: string\n pressed: string\n /** The accent as text on the light surface, at 4.5:1. */\n textLight: string\n /** The accent as text on the dark surface, at 4.5:1. */\n textDark: string\n}\n\n/** An accent the Theme settings sheet offers by name (Figma's Accent modes). */\nexport interface ThemeNamedAccent {\n id: string\n label: string\n accent: ThemeAccent\n}\n\n/** A swatch of the accent picker. */\nexport interface ThemeAccentSwatch {\n id: string\n label: string\n color: string\n}\n\nexport type ThemeDensityId = 'comfortable' | 'compact'\n\n/** One density's sizes, in points (Apple) and dp (Android). */\nexport interface ThemeDensity {\n id: ThemeDensityId\n label: string\n space2: number\n space4: number\n space6: number\n space8: number\n space10: number\n space12: number\n space16: number\n space20: number\n space24: number\n space32: number\n space40: number\n space48: number\n space64: number\n radiusXs: number\n radiusSm: number\n radiusMd: number\n radiusLg: number\n radiusXl: number\n radius2xl: number\n radiusFull: number\n rowHeight: number\n controlHeight: number\n listRowHeight: number\n phoneRowHeight: number\n titlebarHeight: number\n}\n\n/** The four colors Figma previews a theme by. */\nexport interface ThemePreview {\n rail: string\n surface: string\n canvas: string\n accent: string\n}\n\n// ── Color arithmetic ────────────────────────────────────────────────────────\n\nconst HEX = /^#([0-9a-f]{6})([0-9a-f]{2})?$/i\n\n/** `[r, g, b]` in 0–255, or null for anything that is not `#RRGGBB(AA)`. */\nfunction channels(color: string): [number, number, number] | null {\n const match = HEX.exec(color ?? '')\n if (!match) return null\n const value = match[1]\n return [0, 2, 4].map((index) => parseInt(value.slice(index, index + 2), 16)) as [number, number, number]\n}\n\nfunction hexOf(rgb: readonly number[]): string {\n return `#${rgb\n .map((value) => Math.round(Math.min(255, Math.max(0, value))).toString(16).padStart(2, '0'))\n .join('')}`\n}\n\n/** Whether a value is `#RRGGBB`. */\nexport function isThemeColor(value: unknown): value is string {\n return typeof value === 'string' && /^#[0-9a-f]{6}$/i.test(value)\n}\n\nfunction linear(channel: number): number {\n const value = channel / 255\n return value <= 0.03928 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4\n}\n\nfunction luminance(rgb: readonly number[]): number {\n return 0.2126 * linear(rgb[0]) + 0.7152 * linear(rgb[1]) + 0.0722 * linear(rgb[2])\n}\n\n/** WCAG 2 contrast between two `#RRGGBB` colors, as the console's `contrastRatio` measures it. */\nexport function themeContrast(a: string, b: string): number {\n const x = channels(a)\n const y = channels(b)\n if (!x || !y) return 1\n const [high, low] = [luminance(x), luminance(y)].sort((p, q) => q - p)\n return (high + 0.05) / (low + 0.05)\n}\n\nfunction toHsl([r, g, b]: readonly number[]): [number, number, number] {\n const [red, green, blue] = [r / 255, g / 255, b / 255]\n const max = Math.max(red, green, blue)\n const min = Math.min(red, green, blue)\n const lightness = (max + min) / 2\n if (max === min) return [0, 0, lightness]\n const delta = max - min\n const saturation = lightness > 0.5 ? delta / (2 - max - min) : delta / (max + min)\n let hue: number\n if (max === red) hue = (green - blue) / delta + (green < blue ? 6 : 0)\n else if (max === green) hue = (blue - red) / delta + 2\n else hue = (red - green) / delta + 4\n return [hue / 6, saturation, lightness]\n}\n\nfunction fromHsl(hue: number, saturation: number, lightness: number): string {\n const l = Math.min(1, Math.max(0, lightness))\n if (saturation === 0) return hexOf([l * 255, l * 255, l * 255])\n const q = l < 0.5 ? l * (1 + saturation) : l + saturation - l * saturation\n const p = 2 * l - q\n const channel = (t: number) => {\n let x = t\n if (x < 0) x += 1\n if (x > 1) x -= 1\n if (x < 1 / 6) return p + (q - p) * 6 * x\n if (x < 1 / 2) return q\n if (x < 2 / 3) return p + (q - p) * (2 / 3 - x) * 6\n return p\n }\n return hexOf([channel(hue + 1 / 3) * 255, channel(hue) * 255, channel(hue - 1 / 3) * 255])\n}\n\n/** `color` with its HSL lightness moved by `delta` (−1 to 1). */\nfunction shiftLightness(color: string, delta: number): string {\n const rgb = channels(color)\n if (!rgb) return color\n const [h, s, l] = toHsl(rgb)\n return fromHsl(h, s, l + delta)\n}\n\n/**\n * `color` walked in 2% HSL-lightness steps (darker, or lighter) until it\n * reaches `minContrast` against `against`; the pole when nothing reaches it.\n */\nfunction walkToContrast(color: string, against: string, minContrast: number, direction: -1 | 1): string {\n let candidate = color\n for (let step = 0; step <= 50; step += 1) {\n candidate = shiftLightness(color, direction * 0.02 * step)\n if (themeContrast(candidate, against) >= minContrast) return candidate\n }\n return candidate\n}\n\n/** `from` moved `amount` (0–1) of the way to `to`, per sRGB channel. */\nexport function themeLerp(from: string, to: string, amount: number): string {\n const a = channels(from)\n const b = channels(to)\n if (!a || !b) return from\n return hexOf(a.map((value, index) => value + (b[index] - value) * amount))\n}\n\n/** `color` with an alpha byte: `#RRGGBBAA`. */\nfunction withAlpha(color: string, alpha: number): string {\n const rgb = channels(color)\n if (!rgb) return color\n return `${hexOf(rgb)}${Math.round(alpha * 255).toString(16).padStart(2, '0')}`\n}\n\n// ── Themes ──────────────────────────────────────────────────────────────────\n\n/** Text that must read on every surface: WCAG AA. */\nexport const THEME_TEXT_CONTRAST = 4.5\n\n/** An icon, a border or an indicator: WCAG AA for non-text. */\nexport const THEME_NON_TEXT_CONTRAST = 3\n\nconst DANGER_LIGHT = { dangerFill: '#b3261e', dangerFillHover: '#9c211a', dangerFillPressed: '#861c16' }\nconst DANGER_DARK = { dangerFill: '#c93a31', dangerFillHover: '#b3261e', dangerFillPressed: '#9c211a' }\n\n/**\n * Figma's Aglyn mode as it stood before that mode moved to the console's\n * scheme (2026-10-19): the base Quiet's status colors and the Ocean and Warm\n * derivations build on, as the native shells' themes already do. Kept by\n * value, so moving the shipping Aglyn look moves nothing else.\n */\nconst FIGMA_AGLYN_LIGHT: ThemeColors = {\n canvas: '#f5f5f5',\n surface: '#ffffff',\n raised: '#ffffff',\n sunken: '#edf0f4',\n rail: '#e6eaf0',\n hover: '#eef1f5',\n titlebar: '#fafbfc',\n borderSubtle: '#e3e7ec',\n borderStrong: '#c8d0d9',\n textPrimary: '#1f2329',\n textSecondary: '#5e6773',\n textTertiary: '#7a838e',\n successBg: '#e5f3e6',\n successFg: '#2e6f31',\n warningBg: '#fff0da',\n warningFg: '#85560f',\n dangerBg: '#fce6e5',\n dangerFg: '#a8231e',\n infoBg: '#e3f0fb',\n infoFg: '#10548f',\n newBg: '#f5e4f9',\n newFg: '#8e2aa1',\n scrim: '#0f172152',\n ...DANGER_LIGHT,\n}\n\nconst FIGMA_AGLYN_DARK: ThemeColors = {\n canvas: '#161c21',\n surface: '#1e262e',\n raised: '#27313b',\n sunken: '#12181d',\n rail: '#0f1418',\n hover: '#2a343e',\n titlebar: '#1a2128',\n borderSubtle: '#2e3944',\n borderStrong: '#45525f',\n textPrimary: '#f3f5f7',\n textSecondary: '#b3bcc6',\n textTertiary: '#8a96a3',\n successBg: '#1b3320',\n successFg: '#82c785',\n warningBg: '#3a2c14',\n warningFg: '#ffc479',\n dangerBg: '#3b1a1a',\n dangerFg: '#ed7876',\n infoBg: '#132c42',\n infoFg: '#5da1dc',\n newBg: '#3a1e44',\n newFg: '#e979fc',\n scrim: '#0000008c',\n ...DANGER_DARK,\n}\n\n/** A light and a dark palette. */\nexport interface ThemeColorsPair {\n light: ThemeColors\n dark: ThemeColors\n}\n\n/**\n * The palettes `deriveThemeFromPreview` starts from (the original Figma Aglyn\n * mode), exported so each platform can port the derivation and replay its\n * cases against the same base.\n */\nexport const THEME_FIGMA_BASE: ThemeColorsPair = { light: FIGMA_AGLYN_LIGHT, dark: FIGMA_AGLYN_DARK }\n\n/**\n * The Aglyn theme is the look that ships: the console's resolved MUI scheme\n * (`getConsoleTheme`, the same scheme the native shared tokens are generated\n * from), with translucent text, dividers and hover composited onto the paper\n * they sit on, and the status colors as the console's MUI Alert draws them\n * (standard: the intent's light shade lightened 90% for the ground and\n * darkened 60% for the text in light, the reverse in dark). Where the console\n * scheme has no such color (sunken, rail, titlebar, the strong border, the\n * \"new\" status, the scrim) Figma's Aglyn mode supplies it, and so does the\n * danger FILL: the console's `error.main` carries white text at 4.36:1, under\n * the 4.5:1 a button label owes. `apps/console/specs/native-theme-aglyn.spec.ts`\n * holds these to the console's scheme.\n */\nconst AGLYN_LIGHT: ThemeColors = {\n ...FIGMA_AGLYN_LIGHT,\n canvas: '#f5f5f5',\n surface: '#ffffff',\n raised: '#ffffff',\n hover: '#f5f5f5',\n borderSubtle: '#e0e0e0',\n textPrimary: '#212121',\n textSecondary: '#666666',\n textTertiary: '#9e9e9e',\n successBg: '#f0f8f1',\n successFg: '#2c4c2e',\n warningBg: '#fff8ef',\n warningFg: '#664a28',\n dangerBg: '#fceeed',\n dangerFg: '#5c2220',\n infoBg: '#ecf4fb',\n infoFg: '#1c3a56',\n}\n\nconst AGLYN_DARK: ThemeColors = {\n ...FIGMA_AGLYN_DARK,\n canvas: '#161c21',\n surface: '#2a3440',\n raised: '#2a3440',\n hover: '#3b444f',\n borderSubtle: '#444c57',\n textPrimary: '#ffffff',\n textSecondary: '#bfc2c6',\n textTertiary: '#959aa0',\n successBg: '#0b130b',\n successFg: '#c5e5c7',\n warningBg: '#19120a',\n warningFg: '#ffe3c1',\n dangerBg: '#170c0b',\n dangerFg: '#f7c9c8',\n infoBg: '#091015',\n infoFg: '#bfd9f1',\n}\n\n/** The Aglyn theme's fields the console's scheme supplies; the rest are Figma's Aglyn mode. */\nexport const THEME_CONSOLE_FIELDS: readonly string[] = [\n 'canvas',\n 'surface',\n 'raised',\n 'hover',\n 'borderSubtle',\n 'textPrimary',\n 'textSecondary',\n 'textTertiary',\n 'successBg',\n 'successFg',\n 'warningBg',\n 'warningFg',\n 'dangerBg',\n 'dangerFg',\n 'infoBg',\n 'infoFg',\n]\n\nconst QUIET_LIGHT: ThemeColors = {\n ...FIGMA_AGLYN_LIGHT,\n canvas: '#f4f4f2',\n surface: '#ffffff',\n raised: '#ffffff',\n sunken: '#ecece9',\n rail: '#eaeae7',\n hover: '#efefec',\n titlebar: '#fafaf8',\n borderSubtle: '#e2e2de',\n borderStrong: '#cbcbc5',\n textPrimary: '#222220',\n textSecondary: '#5f5f5a',\n textTertiary: '#7c7c76',\n}\n\nconst QUIET_DARK: ThemeColors = {\n ...FIGMA_AGLYN_DARK,\n canvas: '#1a1a19',\n surface: '#222221',\n raised: '#2b2b29',\n sunken: '#141413',\n rail: '#121211',\n hover: '#30302e',\n titlebar: '#1e1e1d',\n borderSubtle: '#353533',\n borderStrong: '#4a4a47',\n textPrimary: '#f2f2ef',\n textSecondary: '#b5b5af',\n textTertiary: '#8e8e88',\n}\n\nconst NIGHT: ThemeColors = {\n canvas: '#0b1120',\n surface: '#121a2b',\n raised: '#1a2337',\n sunken: '#070c17',\n rail: '#050913',\n hover: '#1e2840',\n titlebar: '#0e1525',\n borderSubtle: '#222c42',\n borderStrong: '#33405c',\n textPrimary: '#eef2fa',\n textSecondary: '#a9b4cb',\n textTertiary: '#8290ab',\n successBg: '#12301f',\n successFg: '#7bcb8f',\n warningBg: '#352914',\n warningFg: '#f6be6e',\n dangerBg: '#3a1820',\n dangerFg: '#f08a8f',\n infoBg: '#122a47',\n infoFg: '#79b2ee',\n newBg: '#36193f',\n newFg: '#e68af6',\n scrim: '#00000099',\n ...DANGER_DARK,\n}\n\nconst HIGH_CONTRAST_LIGHT: ThemeColors = {\n canvas: '#ffffff',\n surface: '#ffffff',\n raised: '#ffffff',\n sunken: '#f0f0f0',\n rail: '#efefef',\n hover: '#e4e4e4',\n titlebar: '#ffffff',\n borderSubtle: '#6b6b6b',\n borderStrong: '#000000',\n textPrimary: '#000000',\n textSecondary: '#1f1f1f',\n textTertiary: '#3d3d3d',\n successBg: '#ddf3de',\n successFg: '#0b4d10',\n warningBg: '#ffe9c7',\n warningFg: '#5c3a00',\n dangerBg: '#fadbd9',\n dangerFg: '#7a0f0b',\n infoBg: '#ddebfa',\n infoFg: '#0a3a66',\n newBg: '#f1ddf6',\n newFg: '#5e0f70',\n scrim: '#00000080',\n ...DANGER_LIGHT,\n}\n\nconst HIGH_CONTRAST_DARK: ThemeColors = {\n canvas: '#000000',\n surface: '#000000',\n raised: '#0a0a0a',\n sunken: '#141414',\n rail: '#0f0f0f',\n hover: '#262626',\n titlebar: '#000000',\n borderSubtle: '#9a9a9a',\n borderStrong: '#ffffff',\n textPrimary: '#ffffff',\n textSecondary: '#ededed',\n textTertiary: '#c7c7c7',\n successBg: '#0b2a0e',\n successFg: '#9be7a0',\n warningBg: '#2e2000',\n warningFg: '#ffd08a',\n dangerBg: '#330b0a',\n dangerFg: '#ff9e9b',\n infoBg: '#0a2340',\n infoFg: '#9ccbff',\n newBg: '#2e0b38',\n newFg: '#f2a8ff',\n scrim: '#000000b2',\n ...DANGER_DARK,\n}\n\n/** Figma's previews of the two themes it does not hold in full. */\nexport const THEME_PREVIEWS: Readonly<Record<'ocean' | 'warm', ThemePreview>> = {\n ocean: { rail: '#0b3954', surface: '#f2f8fb', canvas: '#e3eef4', accent: '#087e8b' },\n warm: { rail: '#e9dccb', surface: '#fffaf3', canvas: '#f5ece0', accent: '#c2571a' },\n}\n\n/**\n * The rail color a theme's DARK appearance is tinted toward. Ocean's rail is\n * already dark, so it is its own seed; Warm's is a light sand, so its dark\n * appearance leans to a dark sand instead.\n */\nexport const THEME_DARK_RAIL_SEEDS: Readonly<Record<'ocean' | 'warm', string>> = {\n ocean: '#0b3954',\n warm: '#7a5634',\n}\n\n/**\n * A theme's full palette from Figma's four-color preview (AGL-3950), by the\n * same rules for both:\n *\n * Light: the preview's canvas, surface and rail as given; raised = surface;\n * sunken = canvas 25% toward the rail; hover = surface 10% toward the rail;\n * titlebar = surface 35% toward the canvas; the borders are Figma's Aglyn\n * mode's, 18% (subtle) and 22% (strong) toward the rail. Text, status, scrim\n * and the danger fill are Figma's Aglyn mode's.\n *\n * Dark: Figma's Aglyn dark mode tinted toward the dark rail seed: canvas\n * 22%, surface and raised 18%, rail 45%; everything else is that mode's.\n *\n * \"Toward\" is a straight per-channel sRGB blend, rounded to the byte.\n */\nexport function deriveThemeFromPreview(preview: ThemePreview, darkRailSeed: string): {\n light: ThemeColors\n dark: ThemeColors\n} {\n return {\n light: {\n ...FIGMA_AGLYN_LIGHT,\n canvas: preview.canvas,\n surface: preview.surface,\n raised: preview.surface,\n rail: preview.rail,\n sunken: themeLerp(preview.canvas, preview.rail, 0.25),\n hover: themeLerp(preview.surface, preview.rail, 0.1),\n titlebar: themeLerp(preview.surface, preview.canvas, 0.35),\n borderSubtle: themeLerp(FIGMA_AGLYN_LIGHT.borderSubtle, preview.rail, 0.18),\n borderStrong: themeLerp(FIGMA_AGLYN_LIGHT.borderStrong, preview.rail, 0.22),\n },\n dark: {\n ...FIGMA_AGLYN_DARK,\n canvas: themeLerp(FIGMA_AGLYN_DARK.canvas, darkRailSeed, 0.22),\n surface: themeLerp(FIGMA_AGLYN_DARK.surface, darkRailSeed, 0.18),\n raised: themeLerp(FIGMA_AGLYN_DARK.raised, darkRailSeed, 0.18),\n rail: themeLerp(FIGMA_AGLYN_DARK.rail, darkRailSeed, 0.45),\n },\n }\n}\n\nconst OCEAN = deriveThemeFromPreview(THEME_PREVIEWS.ocean, THEME_DARK_RAIL_SEEDS.ocean)\nconst WARM = deriveThemeFromPreview(THEME_PREVIEWS.warm, THEME_DARK_RAIL_SEEDS.warm)\n\n/** The Aglyn blue every Figma theme's \"Accent: Theme\" resolves to. */\nconst AGLYN_ACCENT = '#00b0ff'\n\n/** Every theme, in the order the Theme settings sheet lists them. */\nexport const THEME_PALETTES: readonly ThemePalette[] = [\n { id: 'aglyn', label: '', brandLabel: true, light: AGLYN_LIGHT, dark: AGLYN_DARK, accent: AGLYN_ACCENT, source: 'console' },\n { id: 'quiet', label: 'Quiet', light: QUIET_LIGHT, dark: QUIET_DARK, accent: AGLYN_ACCENT, source: 'figma' },\n { id: 'night', label: 'Night', light: NIGHT, dark: NIGHT, accent: AGLYN_ACCENT, alwaysDark: true, source: 'figma' },\n { id: 'ocean', label: 'Ocean', light: OCEAN.light, dark: OCEAN.dark, accent: THEME_PREVIEWS.ocean.accent, source: 'derived' },\n { id: 'warm', label: 'Warm', light: WARM.light, dark: WARM.dark, accent: THEME_PREVIEWS.warm.accent, source: 'derived' },\n {\n id: 'highContrast',\n label: 'High contrast',\n light: HIGH_CONTRAST_LIGHT,\n dark: HIGH_CONTRAST_DARK,\n accent: AGLYN_ACCENT,\n source: 'figma',\n },\n]\n\n/**\n * The Aglyn theme on a PHONE: the brand's bold surfaces (AGL-3990). Zach chose\n * the bold look for the phone on 2026-10-19 and kept every other form factor\n * on the console's neutral scheme (\"I don't like this light blue everywhere\",\n * on the desktop): so only an Aglyn or Aglyn POS PHONE, in the Aglyn theme with\n * the brand blue as its accent, draws these. A tablet, an iPad, a Mac, a\n * Windows or Mac desktop and the POS register keep `THEME_PALETTES`' Aglyn;\n * every other theme, a picked accent and a white-label brand look the same on\n * a phone as anywhere else.\n *\n * The values are Figma's Theme mode 223:0, \"Aglyn · phone (bold) 10/19\", the\n * explicit mode on every frame of the Native App file's Phone page (2:7). A\n * sky-tinted ground under white panels in light; navy in dark. The strong\n * border is the brand blue at 40% over the white surface, flattened. The\n * statuses are the console's intents, tinted to sit on the sky ground; info's\n * ink in light is the brand's ink, and in dark it is the accent's dark text\n * shade (#4DC8FF), never the fill (Zach, 2026-10-19: blue as text on a dark\n * ground is the console's `primary.dark`).\n */\nconst PHONE_AGLYN_LIGHT: ThemeColors = {\n canvas: '#edf9ff',\n surface: '#ffffff',\n raised: '#ffffff',\n sunken: '#e6f7ff',\n rail: '#e0f6ff',\n hover: '#f2fbff',\n titlebar: '#f7fdff',\n borderSubtle: '#dbf4ff',\n borderStrong: '#66d0ff',\n textPrimary: '#19202a',\n textSecondary: '#404c5c',\n textTertiary: '#5d6d85',\n successBg: '#e2f2e3',\n successFg: '#306f33',\n warningBg: '#ffebd1',\n warningFg: '#975400',\n dangerBg: '#fbdddc',\n dangerFg: '#ba1c18',\n infoBg: '#ccefff',\n infoFg: '#07171f',\n newBg: '#fae0fe',\n newFg: '#a604c2',\n scrim: '#0f172152',\n ...DANGER_LIGHT,\n}\n\nconst PHONE_AGLYN_DARK: ThemeColors = {\n canvas: '#07181f',\n surface: '#0d2029',\n raised: '#152b35',\n sunken: '#1a323d',\n rail: '#061319',\n hover: '#213a45',\n titlebar: '#0a1c24',\n borderSubtle: '#263f4a',\n borderStrong: '#395460',\n textPrimary: '#f2f7fb',\n textSecondary: '#c1d0d7',\n textTertiary: '#8ba5b1',\n successBg: '#1d4433',\n successFg: '#72c275',\n warningBg: '#4c432f',\n warningFg: '#ffab40',\n dangerBg: '#452228',\n dangerFg: '#ee7b78',\n infoBg: '#0a3d54',\n infoFg: '#4dc8ff',\n newBg: '#442860',\n newFg: '#ea7cfc',\n scrim: '#0000008c',\n ...DANGER_DARK,\n}\n\n/** The Aglyn theme's grounds on a phone, light and dark (Figma Theme mode 223:0): see `PHONE_AGLYN_LIGHT`. */\nexport const THEME_PHONE_AGLYN: ThemeColorsPair = { light: PHONE_AGLYN_LIGHT, dark: PHONE_AGLYN_DARK }\n\n/** The accent the phone's bold look belongs to: the brand blue, the Aglyn theme's own. */\nexport const THEME_PHONE_BOLD_ACCENT = AGLYN_ACCENT\n\n// ── Accents ─────────────────────────────────────────────────────────────────\n\n/** The surfaces an accent's text is held to. */\nconst LIGHT_PAGE = AGLYN_LIGHT.surface\nconst DARK_PAGE = AGLYN_DARK.surface\n\nfunction accentText(raw: string): Pick<ThemeAccent, 'textLight' | 'textDark'> {\n return {\n textLight: walkToContrast(raw, LIGHT_PAGE, THEME_TEXT_CONTRAST, -1),\n textDark: walkToContrast(raw, DARK_PAGE, THEME_TEXT_CONTRAST, 1),\n }\n}\n\n/**\n * Figma's Accent modes, exactly as the Accent collection holds them.\n *\n * Blue IS the console's primary (AGL-3990). Zach, 2026-10-19: \"Just like the\n * web app the solid buttons should have white text and the brand blue when it\n * is on a light background should use the other shade of blue\". So the fill\n * is `primary.main` #00B0FF under `contrastText` white (the console's own\n * pairing, knowingly under 4.5:1, AGL-1293), the text shades are\n * `primary.dark` per scheme (#0077AD light, #4DC8FF dark: what MuiLink and the\n * text and outlined MuiButton labels draw, AGL-1186), and the hover fill is\n * `primary.hover`. #0073AE is retired. A mode that names its own text shades\n * keeps them; the others walk their ring to 4.5:1.\n */\nconst FIGMA_ACCENTS: ReadonlyArray<{\n id: string\n label: string\n accent: Omit<ThemeAccent, 'textLight' | 'textDark'> & Partial<Pick<ThemeAccent, 'textLight' | 'textDark'>>\n}> = [\n {\n id: 'blue',\n label: 'Blue',\n accent: {\n fill: '#00b0ff',\n icon: '#00b0ff',\n soft: '#00b0ff29',\n ring: '#00b0ff',\n on: '#ffffff',\n hover: '#0077ad',\n pressed: '#006999',\n textLight: '#0077ad',\n textDark: '#4dc8ff',\n },\n },\n {\n id: 'violet',\n label: 'Violet',\n accent: {\n fill: '#5b3fd6',\n icon: '#8c72f5',\n soft: '#7a5cf02e',\n ring: '#7a5cf0',\n on: '#ffffff',\n hover: '#4e34c2',\n pressed: '#4129aa',\n },\n },\n {\n id: 'pink',\n label: 'Pink',\n accent: {\n fill: '#e040fb',\n icon: '#c93ae3',\n soft: '#e040fb29',\n ring: '#e040fb',\n on: '#ffffff',\n hover: '#cc2ee6',\n pressed: '#b523cf',\n },\n },\n {\n id: 'workspaceBrand',\n label: 'Workspace brand',\n accent: {\n fill: '#2e7d5b',\n icon: '#2e8f66',\n soft: '#2e7d5b2e',\n ring: '#3fa77a',\n on: '#ffffff',\n hover: '#276c4e',\n pressed: '#205a41',\n },\n },\n]\n\n/** The named accents, Figma's own values with their text shades (their own where they name them). */\nexport const THEME_ACCENTS: readonly ThemeNamedAccent[] = FIGMA_ACCENTS.map(({ id, label, accent }) => ({\n id,\n label,\n accent: { ...accentText(accent.ring), ...accent },\n}))\n\n/**\n * Blues the brand has retired (AGL-3990), and the color each now reads as. The\n * picker's old \"Brand blue\" swatch was #0073AE; an accent an account saved\n * from it (`appTheme.accent`) reads as the brand blue, #00B0FF.\n */\nexport const THEME_RETIRED_ACCENTS: Readonly<Record<string, string>> = {\n '#0073ae': '#00b0ff',\n}\n\n/** The accent picker's swatches, in Figma's order. #00B0FF is the one brand blue (AGL-3990), so \"Sky\" went. */\nexport const THEME_ACCENT_SWATCHES: readonly ThemeAccentSwatch[] = [\n { id: 'aglynBlue', label: 'Brand blue', color: '#00b0ff' },\n { id: 'violet', label: 'Violet', color: '#7a5cf0' },\n { id: 'pink', label: 'Pink', color: '#e040fb' },\n { id: 'green', label: 'Green', color: '#2e7d5b' },\n { id: 'warm', label: 'Warm', color: '#c2571a' },\n]\n\n/**\n * Every control's shade of one accent color (AGL-3950).\n *\n * A retired blue (`THEME_RETIRED_ACCENTS`) reads as the color that replaced\n * it. A color one of Figma's Accent modes is drawn from (its ring) answers\n * with that mode exactly: Figma wins where it defines a value. Blue, the brand\n * blue, is one of them, and wears the console's own pairing. Any other color\n * is derived:\n *\n * 1. White text on the color reads at 3:1 or better: the fill is the color,\n * `on` is white. Content on a filled control is always white (Zach,\n * 2026-10-19: \"Just like the web app the solid buttons should have white\n * text\"), and a brand color white reads on at the large-text bar keeps\n * its own fill rather than moving off the brand, the console's own\n * pairing; this is Figma's Pink (#E040FB, 3.34:1).\n * 2. Otherwise the fill is the color darkened in 2% HSL-lightness steps until\n * white reaches 4.5:1, and `on` is white. The 3:1 threshold is the one\n * that sends the workspace green (2.99:1) here, as Figma draws it.\n *\n * hover and pressed are the fill 4% and 8% darker (HSL lightness), soft is\n * the color at 16% alpha, ring is the color, icon is the color darkened in 2%\n * steps until it reaches 3:1 on the light surface, and the two text shades\n * walk it (darker on light, lighter on dark) until 4.5:1 on that surface.\n * Null for anything that is not `#RRGGBB`.\n */\nexport function deriveAccent(color: string): ThemeAccent | null {\n if (!isThemeColor(color)) return null\n const raw = THEME_RETIRED_ACCENTS[color.toLowerCase()] ?? color.toLowerCase()\n const named = THEME_ACCENTS.find((entry) => entry.accent.ring === raw)\n if (named) return { ...named.accent }\n const white = '#ffffff'\n const fill =\n themeContrast(raw, white) >= THEME_NON_TEXT_CONTRAST ? raw : walkToContrast(raw, white, THEME_TEXT_CONTRAST, -1)\n return {\n fill,\n icon: walkToContrast(raw, LIGHT_PAGE, THEME_NON_TEXT_CONTRAST, -1),\n soft: withAlpha(raw, 0.16),\n ring: raw,\n on: white,\n hover: shiftLightness(fill, -0.04),\n pressed: shiftLightness(fill, -0.08),\n ...accentText(raw),\n }\n}\n\n// ── Density ─────────────────────────────────────────────────────────────────\n\n/** Figma's Density modes. */\nexport const THEME_DENSITIES: readonly ThemeDensity[] = [\n {\n id: 'comfortable',\n label: 'Comfortable',\n space2: 2,\n space4: 4,\n space6: 6,\n space8: 8,\n space10: 10,\n space12: 12,\n space16: 16,\n space20: 20,\n space24: 24,\n space32: 32,\n space40: 40,\n space48: 48,\n space64: 64,\n radiusXs: 4,\n radiusSm: 6,\n radiusMd: 8,\n radiusLg: 12,\n radiusXl: 16,\n radius2xl: 24,\n radiusFull: 999,\n rowHeight: 32,\n controlHeight: 32,\n listRowHeight: 60,\n phoneRowHeight: 52,\n titlebarHeight: 52,\n },\n {\n id: 'compact',\n label: 'Compact',\n space2: 2,\n space4: 4,\n space6: 4,\n space8: 6,\n space10: 8,\n space12: 8,\n space16: 12,\n space20: 16,\n space24: 20,\n space32: 24,\n space40: 32,\n space48: 40,\n space64: 48,\n radiusXs: 4,\n radiusSm: 6,\n radiusMd: 8,\n radiusLg: 12,\n radiusXl: 16,\n radius2xl: 24,\n radiusFull: 999,\n rowHeight: 28,\n controlHeight: 28,\n listRowHeight: 48,\n phoneRowHeight: 44,\n titlebarHeight: 44,\n },\n]\n\n/** A theme by id, Aglyn for anything unknown. */\nexport function themePalette(id: unknown): ThemePalette {\n return THEME_PALETTES.find((entry) => entry.id === id) ?? THEME_PALETTES[0]\n}\n"],"names":["HEX","channels","color","match","exec","value","map","index","parseInt","slice","hexOf","rgb","Math","round","min","max","toString","padStart","join","isThemeColor","test","linear","channel","luminance","themeContrast","a","b","x","y","high","low","sort","p","q","toHsl","r","g","red","green","blue","lightness","delta","saturation","hue","fromHsl","l","t","shiftLightness","h","s","walkToContrast","against","minContrast","direction","candidate","step","themeLerp","from","to","amount","withAlpha","alpha","THEME_TEXT_CONTRAST","THEME_NON_TEXT_CONTRAST","DANGER_LIGHT","dangerFill","dangerFillHover","dangerFillPressed","DANGER_DARK","FIGMA_AGLYN_LIGHT","canvas","surface","raised","sunken","rail","hover","titlebar","borderSubtle","borderStrong","textPrimary","textSecondary","textTertiary","successBg","successFg","warningBg","warningFg","dangerBg","dangerFg","infoBg","infoFg","newBg","newFg","scrim","FIGMA_AGLYN_DARK","THEME_FIGMA_BASE","light","dark","AGLYN_LIGHT","AGLYN_DARK","THEME_CONSOLE_FIELDS","QUIET_LIGHT","QUIET_DARK","NIGHT","HIGH_CONTRAST_LIGHT","HIGH_CONTRAST_DARK","THEME_PREVIEWS","ocean","accent","warm","THEME_DARK_RAIL_SEEDS","deriveThemeFromPreview","preview","darkRailSeed","OCEAN","WARM","AGLYN_ACCENT","THEME_PALETTES","id","label","brandLabel","source","alwaysDark","PHONE_AGLYN_LIGHT","PHONE_AGLYN_DARK","THEME_PHONE_AGLYN","THEME_PHONE_BOLD_ACCENT","LIGHT_PAGE","DARK_PAGE","accentText","raw","textLight","textDark","FIGMA_ACCENTS","fill","icon","soft","ring","on","pressed","THEME_ACCENTS","THEME_RETIRED_ACCENTS","THEME_ACCENT_SWATCHES","deriveAccent","toLowerCase","named","find","entry","white","THEME_DENSITIES","space2","space4","space6","space8","space10","space12","space16","space20","space24","space32","space40","space48","space64","radiusXs","radiusSm","radiusMd","radiusLg","radiusXl","radius2xl","radiusFull","rowHeight","controlHeight","listRowHeight","phoneRowHeight","titlebarHeight","themePalette"],"mappings":"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"}
@@ -177,11 +177,13 @@ const DAY = 24 * 60;
177
177
  // request's billed hours and the Stripe invoice item that carries them
178
178
  // disagree, or one exists without the other. Should know, not must:
179
179
  // nothing is lost yet, the next invoice has not gone, and the fix is on
180
- // the request. The same check compares a flat-quoted project's
180
+ // the request. It also checks each request against its own minutes and
181
+ // approval, so a charge both sides agree on but the ledger does not is
182
+ // caught too (AGL-4031). The same check compares a flat-quoted project's
181
183
  // invoices (AGL-3970), whose findings arrive with `month` "projects".
182
184
  type: 'billing.siteCareReconciliation',
183
185
  label: 'Site Care billing does not match Stripe',
184
- description: 'A Site Care request’s billed hours, amount or line description disagree with its Stripe invoice item, a Stripe item has no request row behind it, a month’s total disagrees with its statement, or a project invoice’s status or amount disagrees with Stripe (or exists in Stripe with no project row). Open the workspace’s staff page and correct it from the request or the project — never by hand in Stripe.',
186
+ description: 'A Site Care request’s billed hours, amount or line description disagree with its Stripe invoice item, a Stripe item has no request row behind it, a request’s billed hours or charge disagree with its own logged minutes and the owner’s approval (whole started hours past the included hour, never below the approved hours), a month’s total disagrees with its statement or with those minutes, or a project invoice’s status or amount disagrees with Stripe (or exists in Stripe with no project row). Open the workspace’s staff page and correct it from the request or the project — never by hand in Stripe.',
185
187
  tier: 'should',
186
188
  category: 'billing',
187
189
  title: 'Site Care billing mismatch on {{orgName}} ({{month}})',