@materialwebunofficial/antigravity-themes 1.0.0 → 1.0.4

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.
package/src/theme-host.js CHANGED
@@ -1,24 +1,33 @@
1
- // Antigravity Themes host: applies one theme from a bundle, remembers the choice, and
1
+ // AGY theme host: applies one theme from a bundle, remembers the choice, and
2
2
  // adds a "Theme" menu to the title bar (Ctrl+Shift+F12 cycles themes; not
3
- // Ctrl+Alt+<letter>: Windows reads Ctrl+Alt as AltGr, which types
4
- // characters on many keyboard layouts). In MD3E the title bar's menus and Theme fold
3
+ // Ctrl+Alt+<letter>: Windows reads Ctrl+Alt as AltGr, and on the Turkish Q
4
+ // layout AltGr+T types "₺"). In MD3E the title bar's menus and Theme fold
5
5
  // into one app menu button (the package's <md-menu>).
6
6
  //
7
- // Context-free on purpose: it only touches the DOM and document.cookie. The
8
- // injector (lib/injector.mjs) evaluates it in every Antigravity window.
7
+ // Context-free on purpose: it only touches the DOM and document.cookie, so it
8
+ // runs the same when inject.mjs evaluates it in the page and when a preload
9
+ // calls it from Electron's isolated world.
9
10
  function agyThemeHost(bundle) {
10
11
  // Bump when this file's code changes. A same-version host only takes the
11
12
  // new bundle; an older one would keep running its old code through
12
13
  // update(), so it is disposed and replaced (same task: no flash).
13
- const HOST_VERSION = 17; // also bump when a module it starts (context-card, turn-tokens, loading-/progress-indicator, code-tools) changes: the host starts them once
14
+ const HOST_VERSION = 20; // also bump when a module it starts (context-card, turn-tokens, loading-/progress-indicator, code-tools) changes: the host starts them once
14
15
  const existing = window.__agyThemeHost;
15
16
  if (existing?.version === HOST_VERSION) return existing.update(bundle);
16
17
  existing?.dispose();
17
18
 
18
19
  const COOKIE = 'agy-theme';
19
- const readChoice = () => (document.cookie.match(/(?:^|;\s*)agy-theme=([^;]+)/) || [])[1];
20
+ const readChoice = () => {
21
+ try {
22
+ return (document.cookie.match(/(?:^|;\s*)agy-theme=([^;]+)/) || [])[1];
23
+ } catch {
24
+ return null;
25
+ }
26
+ };
20
27
  const saveChoice = (key) => {
21
- document.cookie = `${COOKIE}=${key}; Max-Age=31536000; Path=/; SameSite=Strict; Secure`;
28
+ try {
29
+ document.cookie = `${COOKIE}=${key}; Max-Age=31536000; Path=/; SameSite=Strict; Secure`;
30
+ } catch {}
22
31
  };
23
32
 
24
33
  let options = new Map();
@@ -88,21 +97,29 @@ function agyThemeHost(bundle) {
88
97
  ensureFonts();
89
98
  watchMode();
90
99
  syncMode();
91
- root.setAttribute('data-agy-theme', current.theme);
92
- if (current.seed) root.setAttribute('data-agy-seed', current.seed);
93
- else root.removeAttribute('data-agy-seed');
94
- let style = document.getElementById('agy-theme-css');
95
- if (!style) {
96
- style = document.createElement('style');
97
- style.id = 'agy-theme-css';
100
+ if (current.theme === 'antigravity') {
101
+ root.removeAttribute('data-agy-theme');
102
+ root.removeAttribute('data-agy-seed');
103
+ const style = document.getElementById('agy-theme-css');
104
+ if (style) style.textContent = '';
105
+ } else {
106
+ root.setAttribute('data-agy-theme', current.theme);
107
+ if (current.seed) root.setAttribute('data-agy-seed', current.seed);
108
+ else root.removeAttribute('data-agy-seed');
109
+ let style = document.getElementById('agy-theme-css');
110
+ if (!style) {
111
+ style = document.createElement('style');
112
+ style.id = 'agy-theme-css';
113
+ }
114
+ const css = bundle.themes[current.theme] || '';
115
+ if (style.textContent !== css) style.textContent = css;
116
+ // Last in <head> so equal-specificity ties go to the theme
117
+ const parent = document.head || root;
118
+ if (style.parentNode !== parent || style !== parent.lastElementChild) parent.appendChild(style);
98
119
  }
99
- const css = bundle.themes[current.theme] || '';
100
- if (style.textContent !== css) style.textContent = css;
101
- // Last in <head> so equal-specificity ties go to the theme
102
- const parent = document.head || root;
103
- if (style.parentNode !== parent || style !== parent.lastElementChild) parent.appendChild(style);
104
120
  announceTheme();
105
121
  renderMenu();
122
+ if (appMenuBuilt) buildAppMenu(false);
106
123
  if (current.theme !== 'md3e') closeAppMenu();
107
124
  // Themes change the title bar's padding and font: re-anchor the menu
108
125
  requestAnimationFrame(() => { position(); syncTitleBarOverlay(); });
@@ -186,15 +203,25 @@ function agyThemeHost(bundle) {
186
203
  box-shadow: 0 16px 40px -12px rgb(0 0 0 / 0.75);
187
204
  }
188
205
  .menu[hidden] { display: none; }
189
- [role="menuitemradio"] {
206
+ [role="menuitemradio"], .menu-sub-trigger {
190
207
  display: flex; align-items: center; gap: 10px; height: 32px; padding: 0 10px;
191
208
  border-radius: 8px; font: inherit; color: inherit; cursor: default; outline: none;
192
209
  }
193
- [role="menuitemradio"]:hover, [role="menuitemradio"]:focus-visible { background: var(--secondary, #333); }
210
+ [role="menuitemradio"]:hover, [role="menuitemradio"]:focus-visible,
211
+ .menu-sub-trigger:hover, .menu-sub-wrapper:hover .menu-sub-trigger { background: var(--secondary, #333); }
194
212
  .swatch { width: 12px; height: 12px; border-radius: 999px; box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.2); }
195
213
  .name { flex: 1; }
196
214
  .check { visibility: hidden; }
197
215
  [aria-checked="true"] .check { visibility: visible; }
216
+ .chevron { color: var(--muted-foreground, #999); font-size: 13px; margin-left: auto; }
217
+ .menu-sub-wrapper { position: relative; }
218
+ .menu-sub-flyout {
219
+ display: none; position: absolute; top: -6px; left: calc(100% + 4px); min-width: 170px; padding: 6px;
220
+ border: 1px solid var(--border, #333); border-radius: 12px;
221
+ background: var(--card, #1c1c1f); color: var(--foreground, #eee);
222
+ box-shadow: 0 16px 40px -12px rgb(0 0 0 / 0.75);
223
+ }
224
+ .menu-sub-wrapper:hover .menu-sub-flyout { display: block; }
198
225
  .hint { padding: 6px 10px 2px; color: var(--muted-foreground, #999); font-size: 11px; }
199
226
 
200
227
  /* MD3E: the md-menu anatomy of md3e.css section 50 (one
@@ -241,10 +268,13 @@ function agyThemeHost(bundle) {
241
268
 
242
269
  /* MD3E app menu: an xsmall M3 icon button (32px, 20px icon); the
243
270
  <md-menu> it opens draws itself */
244
- .appmenu { display: none; }
271
+ .appmenu { display: none; min-width: 220px; }
245
272
  :host-context([data-agy-theme="md3e"][data-agy-appmenu]) .trigger,
246
273
  :host-context([data-agy-theme="md3e"][data-agy-appmenu]) .menu { display: none !important; }
247
274
  :host-context([data-agy-theme="md3e"][data-agy-appmenu]) .appmenu { display: block; }
275
+ .appmenu md-menu {
276
+ min-width: 220px;
277
+ }
248
278
  .appmenu-trigger {
249
279
  display: grid; place-items: center; width: 32px; height: 32px; padding: 0; border: 0;
250
280
  border-radius: 9999px; background: transparent; color: var(--md-sys-color-on-surface-variant);
@@ -266,18 +296,27 @@ function agyThemeHost(bundle) {
266
296
  trigger.addEventListener('click', () => (menu.hidden ? openMenu() : closeMenu()));
267
297
  menu.addEventListener('click', (e) => {
268
298
  const item = e.target.closest('[role="menuitemradio"]');
269
- if (item) { select(item.dataset.key); closeMenu(); }
299
+ if (item) { select(item.dataset.key); closeMenu(); return; }
300
+ const subTrig = e.target.closest('.menu-sub-trigger');
301
+ if (subTrig) {
302
+ const activeSeed = [...options.values()].find((o) => o.theme === 'md3e' && o.key === current?.key);
303
+ select(activeSeed ? activeSeed.key : 'md3e-teal');
304
+ closeMenu();
305
+ }
270
306
  });
271
307
  menu.addEventListener('keydown', onMenuKey);
272
308
  document.body.appendChild(hostEl);
273
309
  renderMenu();
310
+ if (appMenuBuilt) buildAppMenu(false);
274
311
  position();
275
312
  }
276
313
 
277
314
  function renderMenu() {
278
315
  if (!menu) return;
279
316
  menu.innerHTML = '';
317
+ // Standalone options
280
318
  for (const o of options.values()) {
319
+ if (o.theme === 'md3e') continue;
281
320
  const item = document.createElement('div');
282
321
  item.setAttribute('role', 'menuitemradio');
283
322
  item.setAttribute('aria-checked', String(o.key === current?.key));
@@ -288,6 +327,38 @@ function agyThemeHost(bundle) {
288
327
  item.querySelector('.name').textContent = o.name;
289
328
  menu.appendChild(item);
290
329
  }
330
+ // Group MD3E into single entry with submenu flyout
331
+ const md3eOptions = [...options.values()].filter((o) => o.theme === 'md3e');
332
+ if (md3eOptions.length) {
333
+ const isMd3eActive = current?.theme === 'md3e';
334
+ const subWrapper = document.createElement('div');
335
+ subWrapper.className = 'menu-sub-wrapper';
336
+ const subTrigger = document.createElement('div');
337
+ subTrigger.className = 'menu-sub-trigger';
338
+ subTrigger.setAttribute('role', 'button');
339
+ subTrigger.tabIndex = -1;
340
+ subTrigger.innerHTML = '<span class="swatch" style="background:#a5faf9"></span><span class="name">Material 3 Expressive</span><span class="check">✓</span><span class="chevron">›</span>';
341
+ if (!isMd3eActive) subTrigger.querySelector('.check').style.visibility = 'hidden';
342
+ else subTrigger.querySelector('.check').style.visibility = 'visible';
343
+ subWrapper.appendChild(subTrigger);
344
+
345
+ const flyout = document.createElement('div');
346
+ flyout.className = 'menu-sub-flyout';
347
+ flyout.setAttribute('role', 'menu');
348
+ for (const o of md3eOptions) {
349
+ const item = document.createElement('div');
350
+ item.setAttribute('role', 'menuitemradio');
351
+ item.setAttribute('aria-checked', String(o.key === current?.key));
352
+ item.tabIndex = -1;
353
+ item.dataset.key = o.key;
354
+ item.innerHTML = '<span class="swatch"></span><span class="name"></span><span class="check">✓</span>';
355
+ item.querySelector('.swatch').style.background = o.swatch || 'transparent';
356
+ item.querySelector('.name').textContent = o.name;
357
+ flyout.appendChild(item);
358
+ }
359
+ subWrapper.appendChild(flyout);
360
+ menu.appendChild(subWrapper);
361
+ }
291
362
  const hint = document.createElement('div');
292
363
  hint.className = 'hint';
293
364
  hint.textContent = 'Ctrl+Shift+F12: next theme';
@@ -344,8 +415,25 @@ function agyThemeHost(bundle) {
344
415
  return sub;
345
416
  }
346
417
 
418
+ function patchComponentSheets() {
419
+ try {
420
+ const dummyItem = document.createElement('md-menu-item');
421
+ if (dummyItem.shadowRoot?.adoptedStyleSheets?.length) {
422
+ const s = dummyItem.shadowRoot.adoptedStyleSheets[0];
423
+ s.insertRule('.text { white-space: nowrap !important; overflow-wrap: normal !important; max-width: none !important; }', 0);
424
+ s.insertRule('.item { min-width: 220px !important; max-width: 320px !important; }', 0);
425
+ }
426
+ const dummyMenu = document.createElement('md-menu');
427
+ if (dummyMenu.shadowRoot?.adoptedStyleSheets?.length) {
428
+ const ms = dummyMenu.shadowRoot.adoptedStyleSheets[0];
429
+ ms.insertRule('.menu { min-width: 220px !important; max-width: 320px !important; will-change: transform, opacity !important; }', 0);
430
+ }
431
+ } catch {}
432
+ }
433
+
347
434
  function mountAppMenu() {
348
435
  if (!customElements.get('md-menu')) return;
436
+ patchComponentSheets();
349
437
  appMenu = document.createElement('md-menu');
350
438
  appMenu.className = 'appmenu';
351
439
  appMenu.setAttribute('label', 'Application menu');
@@ -356,13 +444,9 @@ function agyThemeHost(bundle) {
356
444
  appTrigger.title = 'Menu';
357
445
  appTrigger.appendChild(glyph('menu'));
358
446
  appMenu.appendChild(appTrigger);
359
- // The items are built before the press, while the pointer comes over the
360
- // button or it takes focus: built on the click, the ~25 new elements cost
361
- // the opening its first frame. The click and keys only catch a change.
362
- appTrigger.addEventListener('pointerenter', buildAppMenu);
363
- appTrigger.addEventListener('focus', buildAppMenu);
364
- appTrigger.addEventListener('click', buildAppMenu);
365
- appTrigger.addEventListener('keydown', (e) => { if (e.key === 'ArrowDown' || e.key === 'ArrowUp') buildAppMenu(); });
447
+ // these run before the menu's own handlers on its trigger slot
448
+ appTrigger.addEventListener('click', () => buildAppMenu(false));
449
+ appTrigger.addEventListener('keydown', (e) => { if (e.key === 'ArrowDown' || e.key === 'ArrowUp') buildAppMenu(false); });
366
450
  appMenu.addEventListener('select', onAppMenuSelect);
367
451
  shadow.appendChild(appMenu);
368
452
  document.documentElement.setAttribute('data-agy-appmenu', '');
@@ -381,15 +465,55 @@ function agyThemeHost(bundle) {
381
465
  return menus;
382
466
  }
383
467
 
384
- let builtFor = '';
385
- function buildAppMenu() {
386
- if (!appMenu || appMenu.open) return;
387
- const menus = readAppMenus();
388
- // rebuilt only when the app's menus or the chosen theme changed
389
- const signature = JSON.stringify([current?.key, menus.map((m) => [m.title, m.options.map((o) => [String(o.label), o.shortcut, !!o.nonSelectable])])]);
390
- if (signature === builtFor) { appMenus = menus; return; }
391
- builtFor = signature;
392
- appMenus = menus;
468
+ function fixItemStyle(el) {
469
+ if (!el || !el.shadowRoot) return;
470
+ try {
471
+ let s = el.shadowRoot.querySelector('#agy-item-fix');
472
+ if (!s) {
473
+ s = document.createElement('style');
474
+ s.id = 'agy-item-fix';
475
+ s.textContent = `
476
+ .text { white-space: nowrap !important; overflow-wrap: normal !important; max-width: none !important; }
477
+ .item { min-width: 220px !important; max-width: 320px !important; }
478
+ `;
479
+ el.shadowRoot.appendChild(s);
480
+ }
481
+ } catch {}
482
+ }
483
+
484
+ function fixMenuStyle(menu) {
485
+ if (!menu || !menu.shadowRoot) return;
486
+ try {
487
+ let s = menu.shadowRoot.querySelector('#agy-menu-fix');
488
+ if (!s) {
489
+ s = document.createElement('style');
490
+ s.id = 'agy-menu-fix';
491
+ s.textContent = `
492
+ .menu { min-width: 220px !important; max-width: 320px !important; will-change: transform, opacity !important; }
493
+ `;
494
+ menu.shadowRoot.appendChild(s);
495
+ }
496
+ } catch {}
497
+ for (const it of menu.querySelectorAll('md-menu-item')) fixItemStyle(it);
498
+ for (const sub of menu.querySelectorAll('md-menu')) fixMenuStyle(sub);
499
+ }
500
+
501
+ let appMenuBuilt = false;
502
+ function buildAppMenu(force = false) {
503
+ const forced = force === true;
504
+ if (!appMenu || (!forced && appMenu.open)) return;
505
+ if (appMenuBuilt && !forced) {
506
+ window.__buildAppMenuHitCache = (window.__buildAppMenuHitCache || 0) + 1;
507
+ for (const item of appMenu.querySelectorAll('md-menu-item[value^="theme:"]')) {
508
+ const val = item.getAttribute('value') || '';
509
+ item.selected = (val === `theme:${current?.key}`);
510
+ }
511
+ fixMenuStyle(appMenu);
512
+ return;
513
+ }
514
+ window.__buildAppMenuFullBuild = (window.__buildAppMenuFullBuild || 0) + 1;
515
+ appMenus = readAppMenus();
516
+ if (!appMenus.length && !forced) return;
393
517
  const items = appMenus.map((m, i) => {
394
518
  const top = menuItem(m.title);
395
519
  top.append(glyph(MENU_GLYPHS[m.title] || 'info', 'start'), submenu(m.title, m.options.map((o, j) => menuItem(String(o.label), {
@@ -400,23 +524,46 @@ function agyThemeHost(bundle) {
400
524
  }))));
401
525
  return top;
402
526
  });
403
- // Theme: the choices as one single-select group, "Next theme" as another
527
+ // Theme: standalone themes, MD3E submenu, and Next theme
404
528
  const choices = document.createElement('md-menu-group');
405
529
  for (const o of options.values()) {
530
+ if (o.theme === 'md3e') continue;
406
531
  const item = menuItem(o.name, { value: `theme:${o.key}`, 'selection-mode': 'single', selected: o.key === current?.key, 'selected-icon': 'check' });
407
- const swatch = document.createElement('span');
408
- swatch.className = 'swatch';
409
- swatch.slot = 'end';
410
- swatch.style.background = o.swatch || 'transparent';
411
- item.appendChild(swatch);
532
+ if (o.swatch && o.swatch !== 'transparent') {
533
+ const swatch = document.createElement('span');
534
+ swatch.className = 'swatch';
535
+ swatch.slot = 'end';
536
+ swatch.style.background = o.swatch;
537
+ item.appendChild(swatch);
538
+ }
412
539
  choices.appendChild(item);
413
540
  }
541
+ const md3eItems = [];
542
+ for (const o of options.values()) {
543
+ if (o.theme !== 'md3e') continue;
544
+ const item = menuItem(o.name, { value: `theme:${o.key}`, 'selection-mode': 'single', selected: o.key === current?.key, 'selected-icon': 'check' });
545
+ if (o.swatch) {
546
+ const swatch = document.createElement('span');
547
+ swatch.className = 'swatch';
548
+ swatch.slot = 'end';
549
+ swatch.style.background = o.swatch;
550
+ item.appendChild(swatch);
551
+ }
552
+ md3eItems.push(item);
553
+ }
554
+ if (md3eItems.length) {
555
+ const md3eMenu = menuItem('Material 3 Expressive');
556
+ md3eMenu.append(glyph('palette', 'start'), submenu('Material 3 Expressive', md3eItems));
557
+ choices.appendChild(md3eMenu);
558
+ }
414
559
  const cycle = document.createElement('md-menu-group');
415
560
  cycle.appendChild(menuItem('Next theme', { value: 'theme:next', 'trailing-text': 'Ctrl+Shift+F12' }));
416
561
  const theme = menuItem('Theme');
417
562
  theme.append(glyph('palette', 'start'), submenu('Theme', [choices, cycle]));
418
563
  for (const el of [...appMenu.children]) if (el !== appTrigger) el.remove();
419
564
  appMenu.append(...items, theme);
565
+ appMenuBuilt = true;
566
+ fixMenuStyle(appMenu);
420
567
  }
421
568
 
422
569
  function onAppMenuSelect(e) {
@@ -642,6 +789,48 @@ function agyThemeHost(bundle) {
642
789
  window.addEventListener('focus', syncTitleBarOverlay);
643
790
  document.fonts?.addEventListener('loadingdone', position);
644
791
 
792
+ // ---- Virtual list layout guard --------------------------------------------
793
+ // Antigravity virtualizes the sidebar and auxiliary panels with TanStack
794
+ // Virtual. On mounting long lists (e.g. 299 diffs in Review or 95 sidebar
795
+ // items), TanStack Virtual synchronously reads offsetHeight on every
796
+ // [data-index] element while dirtying container styles between each item.
797
+ // In a large conversation, each forced reflow costs 80-100ms, causing a 2s+
798
+ // freeze upon entry. This guard serves known fixed heights or cached values
799
+ // for virtual rows, preventing layout thrashing while letting ResizeObserver
800
+ // handle real dynamic size changes asynchronously on idle frames.
801
+ const origOffsetHeight = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'offsetHeight')?.get;
802
+ const virtualHeightCache = new WeakMap();
803
+ if (origOffsetHeight) {
804
+ Object.defineProperty(HTMLElement.prototype, 'offsetHeight', {
805
+ get() {
806
+ if (this.hasAttribute('data-index')) {
807
+ const cached = virtualHeightCache.get(this);
808
+ if (cached !== undefined) return cached;
809
+ if (this.firstElementChild?.classList.contains('sticky')) {
810
+ virtualHeightCache.set(this, 33);
811
+ return 33;
812
+ }
813
+ if (this.classList.contains('bg-sidebar') || this.querySelector('h2')) {
814
+ virtualHeightCache.set(this, 24);
815
+ return 24;
816
+ }
817
+ if (this.querySelector('.agy-sidebar-row') || this.classList.contains('pb-[1px]')) {
818
+ virtualHeightCache.set(this, 33);
819
+ return 33;
820
+ }
821
+ if (!this.textContent.trim()) {
822
+ virtualHeightCache.set(this, 16);
823
+ return 16;
824
+ }
825
+ }
826
+ const val = origOffsetHeight.call(this);
827
+ if (this.hasAttribute('data-index')) virtualHeightCache.set(this, val);
828
+ return val;
829
+ },
830
+ configurable: true,
831
+ });
832
+ }
833
+
645
834
  // Start the Overview panel's Context card; every theme gets it
646
835
  const contextCard = typeof agyContextCard === 'function' ? agyContextCard() : null;
647
836
  // ...and the token line under each finished turn
@@ -668,6 +857,12 @@ function agyThemeHost(bundle) {
668
857
  loadingIndicator?.dispose();
669
858
  progressIndicators?.dispose();
670
859
  codeTools?.dispose();
860
+ if (origOffsetHeight) {
861
+ Object.defineProperty(HTMLElement.prototype, 'offsetHeight', {
862
+ get: origOffsetHeight,
863
+ configurable: true,
864
+ });
865
+ }
671
866
  document.querySelectorAll('[data-agy-ghost]').forEach((g) => g.remove());
672
867
  document.getElementById('agy-theme-css')?.remove();
673
868
  document.documentElement?.removeAttribute('data-agy-theme');
package/themes/index.json CHANGED
@@ -1,7 +1,10 @@
1
1
  {
2
2
  "default": "matte",
3
3
  "themes": {
4
+ "antigravity": [],
4
5
  "matte": ["matte.css"],
6
+ "paper": ["paper.css"],
7
+ "nordic": ["nordic.css"],
5
8
  "md3e": ["md3e-tokens.css", "md3e.css", "md3e-icons.css"]
6
9
  },
7
10
  "fonts": {
@@ -11,11 +14,14 @@
11
14
  },
12
15
  "ripple": ["md3e"],
13
16
  "options": [
14
- { "key": "matte", "name": "Matte", "theme": "matte", "swatch": "#ededee" },
15
- { "key": "md3e-teal", "name": "Material 3 · Teal", "theme": "md3e", "swatch": "#a5faf9" },
16
- { "key": "md3e-ocean", "name": "Material 3 · Ocean", "theme": "md3e", "seed": "ocean", "swatch": "#9ecefe" },
17
- { "key": "md3e-forest", "name": "Material 3 · Forest", "theme": "md3e", "seed": "forest", "swatch": "#c8f2ae" },
18
- { "key": "md3e-amber", "name": "Material 3 · Amber", "theme": "md3e", "seed": "amber", "swatch": "#fecd79" },
19
- { "key": "md3e-coral", "name": "Material 3 · Coral", "theme": "md3e", "seed": "coral", "swatch": "#ffa6a8" }
17
+ { "key": "antigravity", "name": "Antigravity (Varsayılan)", "theme": "antigravity", "swatch": "transparent" },
18
+ { "key": "matte", "name": "Matte", "theme": "matte", "swatch": "#ededee" },
19
+ { "key": "paper", "name": "E-Ink / Paper", "theme": "paper", "swatch": "#e5e5e5" },
20
+ { "key": "nordic", "name": "Nordic Frost", "theme": "nordic", "swatch": "#88c0d0" },
21
+ { "key": "md3e-teal", "name": "Teal", "theme": "md3e", "seed": "teal", "swatch": "#a5faf9" },
22
+ { "key": "md3e-ocean", "name": "Ocean", "theme": "md3e", "seed": "ocean", "swatch": "#9ecefe" },
23
+ { "key": "md3e-forest", "name": "Forest", "theme": "md3e", "seed": "forest", "swatch": "#c8f2ae" },
24
+ { "key": "md3e-amber", "name": "Amber", "theme": "md3e", "seed": "amber", "swatch": "#fecd79" },
25
+ { "key": "md3e-coral", "name": "Coral", "theme": "md3e", "seed": "coral", "swatch": "#ffa6a8" }
20
26
  ]
21
27
  }