@hbarefoot/engram 1.4.0 → 1.4.2

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.
@@ -130,6 +130,32 @@ function runMigrations(db) {
130
130
  );
131
131
  `);
132
132
 
133
+ // Contradictions table for detected memory conflicts
134
+ db.exec(`
135
+ CREATE TABLE IF NOT EXISTS contradictions (
136
+ id TEXT PRIMARY KEY,
137
+ memory1_id TEXT NOT NULL,
138
+ memory2_id TEXT NOT NULL,
139
+ confidence REAL NOT NULL DEFAULT 0.5,
140
+ reason TEXT,
141
+ category TEXT,
142
+ entity TEXT,
143
+ status TEXT NOT NULL DEFAULT 'unresolved',
144
+ detected_at INTEGER NOT NULL,
145
+ resolved_at INTEGER,
146
+ resolution_action TEXT,
147
+ FOREIGN KEY (memory1_id) REFERENCES memories(id) ON DELETE CASCADE,
148
+ FOREIGN KEY (memory2_id) REFERENCES memories(id) ON DELETE CASCADE
149
+ );
150
+ `);
151
+
152
+ db.exec(`
153
+ CREATE INDEX IF NOT EXISTS idx_contradictions_status ON contradictions(status);
154
+ CREATE INDEX IF NOT EXISTS idx_contradictions_memory1 ON contradictions(memory1_id);
155
+ CREATE INDEX IF NOT EXISTS idx_contradictions_memory2 ON contradictions(memory2_id);
156
+ CREATE INDEX IF NOT EXISTS idx_contradictions_detected_at ON contradictions(detected_at);
157
+ `);
158
+
133
159
  logger.debug('Database migrations completed');
134
160
  }
135
161
 
@@ -624,3 +650,313 @@ function deserializeMemory(row) {
624
650
 
625
651
  return memory;
626
652
  }
653
+
654
+ // --- Contradiction CRUD ---
655
+
656
+ /**
657
+ * Create a contradiction record
658
+ * @param {Database} db
659
+ * @param {Object} contradiction
660
+ * @param {string} contradiction.memory1_id
661
+ * @param {string} contradiction.memory2_id
662
+ * @param {number} contradiction.confidence - 0.0 to 1.0
663
+ * @param {string} contradiction.reason
664
+ * @param {string} [contradiction.category]
665
+ * @param {string} [contradiction.entity]
666
+ * @returns {Object} Created contradiction record
667
+ */
668
+ export function createContradiction(db, contradiction) {
669
+ const id = generateId();
670
+ const now = Date.now();
671
+
672
+ const stmt = db.prepare(`
673
+ INSERT INTO contradictions (id, memory1_id, memory2_id, confidence, reason, category, entity, status, detected_at)
674
+ VALUES (?, ?, ?, ?, ?, ?, ?, 'unresolved', ?)
675
+ `);
676
+
677
+ stmt.run(
678
+ id,
679
+ contradiction.memory1_id,
680
+ contradiction.memory2_id,
681
+ contradiction.confidence,
682
+ contradiction.reason || null,
683
+ contradiction.category || null,
684
+ contradiction.entity || null,
685
+ now
686
+ );
687
+
688
+ logger.debug('Contradiction created', { id, entity: contradiction.entity });
689
+
690
+ return getContradiction(db, id);
691
+ }
692
+
693
+ /**
694
+ * Get a single contradiction by ID with full memory details
695
+ * @param {Database} db
696
+ * @param {string} id
697
+ * @returns {Object|null}
698
+ */
699
+ export function getContradiction(db, id) {
700
+ const stmt = db.prepare(`
701
+ SELECT c.*,
702
+ m1.content as m1_content, m1.category as m1_category, m1.entity as m1_entity,
703
+ m1.confidence as m1_confidence, m1.created_at as m1_created_at,
704
+ m1.namespace as m1_namespace, m1.tags as m1_tags, m1.source as m1_source,
705
+ m2.content as m2_content, m2.category as m2_category, m2.entity as m2_entity,
706
+ m2.confidence as m2_confidence, m2.created_at as m2_created_at,
707
+ m2.namespace as m2_namespace, m2.tags as m2_tags, m2.source as m2_source
708
+ FROM contradictions c
709
+ LEFT JOIN memories m1 ON c.memory1_id = m1.id
710
+ LEFT JOIN memories m2 ON c.memory2_id = m2.id
711
+ WHERE c.id = ?
712
+ `);
713
+
714
+ const row = stmt.get(id);
715
+ if (!row) return null;
716
+
717
+ return deserializeContradiction(row);
718
+ }
719
+
720
+ /**
721
+ * List contradictions with optional filters
722
+ * @param {Database} db
723
+ * @param {Object} [options]
724
+ * @param {string} [options.status] - Filter by status
725
+ * @param {string} [options.category] - Filter by memory category
726
+ * @param {string} [options.sort='detected_at'] - Sort field
727
+ * @param {number} [options.limit=50]
728
+ * @param {number} [options.offset=0]
729
+ * @returns {{ items: Object[], total: number }}
730
+ */
731
+ export function listContradictions(db, options = {}) {
732
+ const {
733
+ status,
734
+ category,
735
+ sort = 'detected_at',
736
+ limit = 50,
737
+ offset = 0
738
+ } = options;
739
+
740
+ const conditions = [];
741
+ const params = [];
742
+
743
+ if (status && status !== 'all') {
744
+ conditions.push('c.status = ?');
745
+ params.push(status);
746
+ }
747
+
748
+ if (category) {
749
+ conditions.push('(m1.category = ? OR m2.category = ?)');
750
+ params.push(category, category);
751
+ }
752
+
753
+ const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
754
+
755
+ // Validate sort to prevent injection
756
+ const sortMap = {
757
+ 'detected_at': 'c.detected_at DESC',
758
+ 'detected_at_asc': 'c.detected_at ASC',
759
+ 'confidence': 'c.confidence DESC'
760
+ };
761
+ const orderBy = sortMap[sort] || 'c.detected_at DESC';
762
+
763
+ // Get total count
764
+ const countStmt = db.prepare(`
765
+ SELECT COUNT(*) as count
766
+ FROM contradictions c
767
+ LEFT JOIN memories m1 ON c.memory1_id = m1.id
768
+ LEFT JOIN memories m2 ON c.memory2_id = m2.id
769
+ ${where}
770
+ `);
771
+ const total = countStmt.get(...params).count;
772
+
773
+ // Get items
774
+ const stmt = db.prepare(`
775
+ SELECT c.*,
776
+ m1.content as m1_content, m1.category as m1_category, m1.entity as m1_entity,
777
+ m1.confidence as m1_confidence, m1.created_at as m1_created_at,
778
+ m1.namespace as m1_namespace, m1.tags as m1_tags, m1.source as m1_source,
779
+ m2.content as m2_content, m2.category as m2_category, m2.entity as m2_entity,
780
+ m2.confidence as m2_confidence, m2.created_at as m2_created_at,
781
+ m2.namespace as m2_namespace, m2.tags as m2_tags, m2.source as m2_source
782
+ FROM contradictions c
783
+ LEFT JOIN memories m1 ON c.memory1_id = m1.id
784
+ LEFT JOIN memories m2 ON c.memory2_id = m2.id
785
+ ${where}
786
+ ORDER BY ${orderBy}
787
+ LIMIT ? OFFSET ?
788
+ `);
789
+
790
+ const rows = stmt.all(...params, limit, offset);
791
+
792
+ return {
793
+ items: rows.map(deserializeContradiction),
794
+ total
795
+ };
796
+ }
797
+
798
+ /**
799
+ * Resolve a contradiction
800
+ * @param {Database} db
801
+ * @param {string} id - Contradiction ID
802
+ * @param {string} action - 'keep_first' | 'keep_second' | 'keep_both' | 'dismiss'
803
+ * @returns {Object|null} Updated contradiction or null if not found
804
+ */
805
+ export function resolveContradiction(db, id, action) {
806
+ const contradiction = getContradiction(db, id);
807
+ if (!contradiction) return null;
808
+
809
+ const now = Date.now();
810
+
811
+ // Perform side effects based on action
812
+ if (action === 'keep_first' && contradiction.memory2) {
813
+ deleteMemory(db, contradiction.memory2.id);
814
+ } else if (action === 'keep_second' && contradiction.memory1) {
815
+ deleteMemory(db, contradiction.memory1.id);
816
+ }
817
+
818
+ const newStatus = action === 'dismiss' ? 'dismissed' : 'resolved';
819
+
820
+ const stmt = db.prepare(`
821
+ UPDATE contradictions
822
+ SET status = ?, resolved_at = ?, resolution_action = ?
823
+ WHERE id = ?
824
+ `);
825
+ stmt.run(newStatus, now, action, id);
826
+
827
+ logger.info('Contradiction resolved', { id, action, status: newStatus });
828
+
829
+ return getContradiction(db, id);
830
+ }
831
+
832
+ /**
833
+ * Check if a contradiction already exists for a memory pair (unresolved only)
834
+ * @param {Database} db
835
+ * @param {string} memory1Id
836
+ * @param {string} memory2Id
837
+ * @returns {boolean}
838
+ */
839
+ export function contradictionExists(db, memory1Id, memory2Id) {
840
+ const stmt = db.prepare(`
841
+ SELECT COUNT(*) as count FROM contradictions
842
+ WHERE status = 'unresolved'
843
+ AND ((memory1_id = ? AND memory2_id = ?) OR (memory1_id = ? AND memory2_id = ?))
844
+ `);
845
+ return stmt.get(memory1Id, memory2Id, memory2Id, memory1Id).count > 0;
846
+ }
847
+
848
+ /**
849
+ * Count unresolved contradictions
850
+ * @param {Database} db
851
+ * @returns {number}
852
+ */
853
+ export function countUnresolvedContradictions(db) {
854
+ const stmt = db.prepare("SELECT COUNT(*) as count FROM contradictions WHERE status = 'unresolved'");
855
+ return stmt.get().count;
856
+ }
857
+
858
+ /**
859
+ * Migrate existing tag-based conflicts to contradictions table.
860
+ * Runs once (checks meta table for flag).
861
+ * @param {Database} db
862
+ * @returns {number} Number of contradictions migrated
863
+ */
864
+ export function migrateTagConflicts(db) {
865
+ // Check if already migrated
866
+ const metaStmt = db.prepare('SELECT value FROM meta WHERE key = ?');
867
+ const migrated = metaStmt.get('contradictions_migrated');
868
+ if (migrated) return 0;
869
+
870
+ // Find all memories with conflict tags
871
+ const memories = listMemories(db, { limit: 10000 });
872
+ const conflicts = memories.filter(m =>
873
+ m.tags && m.tags.some(tag => tag.startsWith('conflict_'))
874
+ );
875
+
876
+ // Group by conflict ID
877
+ const grouped = new Map();
878
+ for (const memory of conflicts) {
879
+ const conflictTags = memory.tags.filter(tag => tag.startsWith('conflict_'));
880
+ for (const conflictId of conflictTags) {
881
+ if (!grouped.has(conflictId)) {
882
+ grouped.set(conflictId, []);
883
+ }
884
+ grouped.get(conflictId).push(memory);
885
+ }
886
+ }
887
+
888
+ let count = 0;
889
+ for (const [, mems] of grouped.entries()) {
890
+ if (mems.length < 2) continue;
891
+
892
+ // Create pairwise contradictions
893
+ for (let i = 0; i < mems.length; i++) {
894
+ for (let j = i + 1; j < mems.length; j++) {
895
+ if (!contradictionExists(db, mems[i].id, mems[j].id)) {
896
+ createContradiction(db, {
897
+ memory1_id: mems[i].id,
898
+ memory2_id: mems[j].id,
899
+ confidence: 0.5,
900
+ reason: 'Legacy tag-based detection',
901
+ category: mems[i].category,
902
+ entity: mems[i].entity
903
+ });
904
+ count++;
905
+ }
906
+ }
907
+ }
908
+ }
909
+
910
+ // Mark as migrated
911
+ db.prepare('INSERT OR REPLACE INTO meta (key, value) VALUES (?, ?)').run(
912
+ 'contradictions_migrated',
913
+ new Date().toISOString()
914
+ );
915
+
916
+ if (count > 0) {
917
+ logger.info('Migrated tag-based conflicts to contradictions table', { count });
918
+ }
919
+
920
+ return count;
921
+ }
922
+
923
+ /**
924
+ * Deserialize a contradiction row with joined memory data
925
+ * @param {Object} row
926
+ * @returns {Object}
927
+ */
928
+ function deserializeContradiction(row) {
929
+ return {
930
+ id: row.id,
931
+ confidence: row.confidence,
932
+ reason: row.reason,
933
+ category: row.category,
934
+ entity: row.entity,
935
+ status: row.status,
936
+ detected_at: row.detected_at,
937
+ resolved_at: row.resolved_at,
938
+ resolution_action: row.resolution_action,
939
+ memory1: row.m1_content ? {
940
+ id: row.memory1_id,
941
+ content: row.m1_content,
942
+ category: row.m1_category,
943
+ entity: row.m1_entity,
944
+ confidence: row.m1_confidence,
945
+ created_at: row.m1_created_at,
946
+ namespace: row.m1_namespace,
947
+ tags: JSON.parse(row.m1_tags || '[]'),
948
+ source: row.m1_source
949
+ } : null,
950
+ memory2: row.m2_content ? {
951
+ id: row.memory2_id,
952
+ content: row.m2_content,
953
+ category: row.m2_category,
954
+ entity: row.m2_entity,
955
+ confidence: row.m2_confidence,
956
+ created_at: row.m2_created_at,
957
+ namespace: row.m2_namespace,
958
+ tags: JSON.parse(row.m2_tags || '[]'),
959
+ source: row.m2_source
960
+ } : null
961
+ };
962
+ }
@@ -4,9 +4,9 @@ import fs from 'fs';
4
4
  import path from 'path';
5
5
  import { fileURLToPath } from 'url';
6
6
  import { loadConfig, getDatabasePath, getModelsPath } from '../config/index.js';
7
- import { initDatabase, createMemory, getMemory, deleteMemory, listMemories, getStats } from '../memory/store.js';
7
+ import { initDatabase, createMemory, getMemory, deleteMemory, listMemories, getStats, listContradictions, resolveContradiction, countUnresolvedContradictions, migrateTagConflicts } from '../memory/store.js';
8
8
  import { recallMemories } from '../memory/recall.js';
9
- import { consolidate, getConflicts } from '../memory/consolidate.js';
9
+ import { consolidate, getConflicts, detectContradictionsForMemory } from '../memory/consolidate.js';
10
10
  import { getOverview, getStaleMemories, getNeverRecalled, getDuplicateClusters, getTrends } from '../memory/analytics.js';
11
11
  import { calculateHealthScore } from '../memory/health.js';
12
12
  import { validateContent } from '../extract/secrets.js';
@@ -41,6 +41,22 @@ function sanitizePaths(paths) {
41
41
  }
42
42
  const __dirname = path.dirname(__filename);
43
43
 
44
+ /**
45
+ * Get the Engram server version.
46
+ * In the esbuild sidecar bundle, process.env.ENGRAM_VERSION is replaced at build time.
47
+ * Otherwise, reads from package.json.
48
+ * @returns {string}
49
+ */
50
+ function getServerVersion() {
51
+ if (process.env.ENGRAM_VERSION) return process.env.ENGRAM_VERSION;
52
+ try {
53
+ const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '../../package.json'), 'utf-8'));
54
+ return pkg.version;
55
+ } catch {
56
+ return 'unknown';
57
+ }
58
+ }
59
+
44
60
  /**
45
61
  * Create and configure the Fastify REST API server
46
62
  * @param {Object} config - Engram configuration
@@ -56,6 +72,13 @@ export function createRESTServer(config) {
56
72
  const db = initDatabase(getDatabasePath(config));
57
73
  const modelsPath = getModelsPath(config);
58
74
 
75
+ // Migrate legacy tag-based conflicts to contradictions table
76
+ try {
77
+ migrateTagConflicts(db);
78
+ } catch (error) {
79
+ logger.warn('Tag conflict migration failed', { error: error.message });
80
+ }
81
+
59
82
  // CORS support
60
83
  fastify.addHook('onRequest', async (request, reply) => {
61
84
  reply.header('Access-Control-Allow-Origin', '*');
@@ -68,10 +91,13 @@ export function createRESTServer(config) {
68
91
  reply.code(204).send();
69
92
  });
70
93
 
94
+ const serverVersion = getServerVersion();
95
+
71
96
  // Health check endpoint
72
97
  fastify.get('/health', async (request, reply) => {
73
98
  return {
74
99
  status: 'healthy',
100
+ version: serverVersion,
75
101
  timestamp: new Date().toISOString(),
76
102
  uptime: process.uptime()
77
103
  };
@@ -101,6 +127,7 @@ export function createRESTServer(config) {
101
127
 
102
128
  return {
103
129
  status: 'ok',
130
+ version: serverVersion,
104
131
  memory: {
105
132
  total: stats.total,
106
133
  withEmbeddings: stats.withEmbeddings,
@@ -192,6 +219,13 @@ export function createRESTServer(config) {
192
219
 
193
220
  logger.info('Memory created via API', { id: memory.id, category: memory.category });
194
221
 
222
+ // Proactive contradiction detection (fire-and-forget)
223
+ setImmediate(() => {
224
+ detectContradictionsForMemory(db, memory).catch(err => {
225
+ logger.warn('Proactive contradiction detection failed', { error: err.message });
226
+ });
227
+ });
228
+
195
229
  return {
196
230
  success: true,
197
231
  memory: {
@@ -380,14 +414,16 @@ export function createRESTServer(config) {
380
414
  detectDuplicates = true,
381
415
  detectContradictions = true,
382
416
  applyDecay = true,
383
- cleanupStale = false
417
+ cleanupStale = false,
418
+ duplicateThreshold
384
419
  } = request.body || {};
385
420
 
386
421
  const results = await consolidate(db, {
387
422
  detectDuplicates,
388
423
  detectContradictions,
389
424
  applyDecay,
390
- cleanupStale
425
+ cleanupStale,
426
+ ...(duplicateThreshold !== undefined && duplicateThreshold !== null && { duplicateThreshold })
391
427
  });
392
428
 
393
429
  logger.info('Consolidation completed via API', results);
@@ -435,6 +471,78 @@ export function createRESTServer(config) {
435
471
  }
436
472
  });
437
473
 
474
+ // --- Contradictions endpoints ---
475
+
476
+ // List contradictions with filtering
477
+ fastify.get('/api/contradictions', async (request, reply) => {
478
+ try {
479
+ const { status, category, sort, limit, offset } = request.query;
480
+
481
+ const result = listContradictions(db, {
482
+ status: status || undefined,
483
+ category: category || undefined,
484
+ sort: sort || 'detected_at',
485
+ limit: limit ? parseInt(limit) : 50,
486
+ offset: offset ? parseInt(offset) : 0
487
+ });
488
+
489
+ const unresolvedCount = countUnresolvedContradictions(db);
490
+
491
+ return {
492
+ success: true,
493
+ contradictions: result.items,
494
+ unresolvedCount,
495
+ pagination: {
496
+ limit: limit ? parseInt(limit) : 50,
497
+ offset: offset ? parseInt(offset) : 0,
498
+ total: result.total
499
+ }
500
+ };
501
+ } catch (error) {
502
+ logger.error('Get contradictions error', { error: error.message });
503
+ reply.code(500);
504
+ return { error: error.message };
505
+ }
506
+ });
507
+
508
+ // Resolve a contradiction
509
+ fastify.post('/api/contradictions/:id/resolve', async (request, reply) => {
510
+ try {
511
+ const { id } = request.params;
512
+ const { action } = request.body || {};
513
+
514
+ const validActions = ['keep_first', 'keep_second', 'keep_both', 'dismiss'];
515
+ if (!action || !validActions.includes(action)) {
516
+ reply.code(400);
517
+ return { error: 'Invalid action. Must be: keep_first, keep_second, keep_both, or dismiss' };
518
+ }
519
+
520
+ const result = resolveContradiction(db, id, action);
521
+ if (!result) {
522
+ reply.code(404);
523
+ return { error: 'Contradiction not found' };
524
+ }
525
+
526
+ return { success: true, contradiction: result };
527
+ } catch (error) {
528
+ logger.error('Resolve contradiction error', { error: error.message });
529
+ reply.code(500);
530
+ return { error: error.message };
531
+ }
532
+ });
533
+
534
+ // Get unresolved contradiction count (for badge)
535
+ fastify.get('/api/contradictions/count', async (request, reply) => {
536
+ try {
537
+ const count = countUnresolvedContradictions(db);
538
+ return { success: true, count };
539
+ } catch (error) {
540
+ logger.error('Contradiction count error', { error: error.message });
541
+ reply.code(500);
542
+ return { error: error.message };
543
+ }
544
+ });
545
+
438
546
  // --- Analytics endpoints ---
439
547
 
440
548
  fastify.get('/api/analytics/overview', async (request, reply) => {
@@ -1 +0,0 @@
1
- *,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.inset-0{top:0;right:0;bottom:0;left:0}.right-2{right:.5rem}.top-2{top:.5rem}.z-50{z-index:50}.mx-3{margin-left:.75rem;margin-right:.75rem}.mx-auto{margin-left:auto;margin-right:auto}.-mb-px{margin-bottom:-1px}.-ml-1{margin-left:-.25rem}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.ml-1{margin-left:.25rem}.ml-2{margin-left:.5rem}.ml-3{margin-left:.75rem}.ml-4{margin-left:1rem}.ml-5{margin-left:1.25rem}.ml-auto{margin-left:auto}.mr-2{margin-right:.5rem}.mr-3{margin-right:.75rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.line-clamp-1{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:1}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-1{height:.25rem}.h-10{height:2.5rem}.h-12{height:3rem}.h-16{height:4rem}.h-2{height:.5rem}.h-3{height:.75rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-64{height:16rem}.h-8{height:2rem}.h-px{height:1px}.max-h-\[500px\]{max-height:500px}.max-h-\[90vh\]{max-height:90vh}.min-h-screen{min-height:100vh}.w-0{width:0px}.w-10{width:2.5rem}.w-12{width:3rem}.w-16{width:4rem}.w-20{width:5rem}.w-3{width:.75rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-8{width:2rem}.w-full{width:100%}.min-w-0{min-width:0px}.max-w-2xl{max-width:42rem}.max-w-4xl{max-width:56rem}.max-w-7xl{max-width:80rem}.max-w-lg{max-width:32rem}.max-w-xs{max-width:20rem}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.cursor-pointer{cursor:pointer}.resize-none{resize:none}.list-inside{list-style-position:inside}.list-decimal{list-style-type:decimal}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-2{gap:.5rem}.gap-2\.5{gap:.625rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-5{gap:1.25rem}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(.5rem * var(--tw-space-x-reverse));margin-left:calc(.5rem * calc(1 - var(--tw-space-x-reverse)))}.space-x-3>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(.75rem * var(--tw-space-x-reverse));margin-left:calc(.75rem * calc(1 - var(--tw-space-x-reverse)))}.space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(1rem * var(--tw-space-x-reverse));margin-left:calc(1rem * calc(1 - var(--tw-space-x-reverse)))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-1\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.375rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.375rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse: 0;border-top-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px * var(--tw-divide-y-reverse))}.divide-gray-200>:not([hidden])~:not([hidden]){--tw-divide-opacity: 1;border-color:rgb(229 231 235 / var(--tw-divide-opacity, 1))}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:1rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.border{border-width:1px}.border-0{border-width:0px}.border-2{border-width:2px}.border-4{border-width:4px}.border-b{border-bottom-width:1px}.border-b-2{border-bottom-width:2px}.border-t{border-top-width:1px}.border-amber-200{--tw-border-opacity: 1;border-color:rgb(253 230 138 / var(--tw-border-opacity, 1))}.border-blue-200{--tw-border-opacity: 1;border-color:rgb(191 219 254 / var(--tw-border-opacity, 1))}.border-gray-100{--tw-border-opacity: 1;border-color:rgb(243 244 246 / var(--tw-border-opacity, 1))}.border-gray-200{--tw-border-opacity: 1;border-color:rgb(229 231 235 / var(--tw-border-opacity, 1))}.border-gray-300{--tw-border-opacity: 1;border-color:rgb(209 213 219 / var(--tw-border-opacity, 1))}.border-green-200{--tw-border-opacity: 1;border-color:rgb(187 247 208 / var(--tw-border-opacity, 1))}.border-orange-200{--tw-border-opacity: 1;border-color:rgb(254 215 170 / var(--tw-border-opacity, 1))}.border-primary-200{--tw-border-opacity: 1;border-color:rgb(186 230 253 / var(--tw-border-opacity, 1))}.border-primary-500{--tw-border-opacity: 1;border-color:rgb(14 165 233 / var(--tw-border-opacity, 1))}.border-primary-600{--tw-border-opacity: 1;border-color:rgb(2 132 199 / var(--tw-border-opacity, 1))}.border-red-200{--tw-border-opacity: 1;border-color:rgb(254 202 202 / var(--tw-border-opacity, 1))}.border-transparent{border-color:transparent}.border-yellow-200{--tw-border-opacity: 1;border-color:rgb(254 240 138 / var(--tw-border-opacity, 1))}.border-t-transparent{border-top-color:transparent}.bg-amber-100{--tw-bg-opacity: 1;background-color:rgb(254 243 199 / var(--tw-bg-opacity, 1))}.bg-amber-50{--tw-bg-opacity: 1;background-color:rgb(255 251 235 / var(--tw-bg-opacity, 1))}.bg-blue-100{--tw-bg-opacity: 1;background-color:rgb(219 234 254 / var(--tw-bg-opacity, 1))}.bg-blue-50{--tw-bg-opacity: 1;background-color:rgb(239 246 255 / var(--tw-bg-opacity, 1))}.bg-gray-100{--tw-bg-opacity: 1;background-color:rgb(243 244 246 / var(--tw-bg-opacity, 1))}.bg-gray-200{--tw-bg-opacity: 1;background-color:rgb(229 231 235 / var(--tw-bg-opacity, 1))}.bg-gray-300{--tw-bg-opacity: 1;background-color:rgb(209 213 219 / var(--tw-bg-opacity, 1))}.bg-gray-50{--tw-bg-opacity: 1;background-color:rgb(249 250 251 / var(--tw-bg-opacity, 1))}.bg-gray-500{--tw-bg-opacity: 1;background-color:rgb(107 114 128 / var(--tw-bg-opacity, 1))}.bg-gray-900{--tw-bg-opacity: 1;background-color:rgb(17 24 39 / var(--tw-bg-opacity, 1))}.bg-green-100{--tw-bg-opacity: 1;background-color:rgb(220 252 231 / var(--tw-bg-opacity, 1))}.bg-green-50{--tw-bg-opacity: 1;background-color:rgb(240 253 244 / var(--tw-bg-opacity, 1))}.bg-green-500{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity, 1))}.bg-green-600{--tw-bg-opacity: 1;background-color:rgb(22 163 74 / var(--tw-bg-opacity, 1))}.bg-orange-50{--tw-bg-opacity: 1;background-color:rgb(255 247 237 / var(--tw-bg-opacity, 1))}.bg-primary-100{--tw-bg-opacity: 1;background-color:rgb(224 242 254 / var(--tw-bg-opacity, 1))}.bg-primary-50{--tw-bg-opacity: 1;background-color:rgb(240 249 255 / var(--tw-bg-opacity, 1))}.bg-primary-600{--tw-bg-opacity: 1;background-color:rgb(2 132 199 / var(--tw-bg-opacity, 1))}.bg-purple-100{--tw-bg-opacity: 1;background-color:rgb(243 232 255 / var(--tw-bg-opacity, 1))}.bg-red-100{--tw-bg-opacity: 1;background-color:rgb(254 226 226 / var(--tw-bg-opacity, 1))}.bg-red-50{--tw-bg-opacity: 1;background-color:rgb(254 242 242 / var(--tw-bg-opacity, 1))}.bg-transparent{background-color:transparent}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.bg-yellow-100{--tw-bg-opacity: 1;background-color:rgb(254 249 195 / var(--tw-bg-opacity, 1))}.bg-yellow-50{--tw-bg-opacity: 1;background-color:rgb(254 252 232 / var(--tw-bg-opacity, 1))}.bg-yellow-600{--tw-bg-opacity: 1;background-color:rgb(202 138 4 / var(--tw-bg-opacity, 1))}.bg-opacity-75{--tw-bg-opacity: .75}.fill-gray-500{fill:#6b7280}.fill-gray-900{fill:#111827}.p-0{padding:0}.p-1{padding:.25rem}.p-12{padding:3rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.p-6{padding:1.5rem}.p-8{padding:2rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-12{padding-top:3rem;padding-bottom:3rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-20{padding-top:5rem;padding-bottom:5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-5{padding-top:1.25rem;padding-bottom:1.25rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pt-1{padding-top:.25rem}.pt-4{padding-top:1rem}.pt-6{padding-top:1.5rem}.text-left{text-align:left}.text-center{text-align:center}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-4xl{font-size:2.25rem;line-height:2.5rem}.text-6xl{font-size:3.75rem;line-height:1}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.capitalize{text-transform:capitalize}.tracking-wider{letter-spacing:.05em}.text-amber-800{--tw-text-opacity: 1;color:rgb(146 64 14 / var(--tw-text-opacity, 1))}.text-amber-900{--tw-text-opacity: 1;color:rgb(120 53 15 / var(--tw-text-opacity, 1))}.text-blue-600{--tw-text-opacity: 1;color:rgb(37 99 235 / var(--tw-text-opacity, 1))}.text-blue-700{--tw-text-opacity: 1;color:rgb(29 78 216 / var(--tw-text-opacity, 1))}.text-blue-800{--tw-text-opacity: 1;color:rgb(30 64 175 / var(--tw-text-opacity, 1))}.text-blue-900{--tw-text-opacity: 1;color:rgb(30 58 138 / var(--tw-text-opacity, 1))}.text-gray-100{--tw-text-opacity: 1;color:rgb(243 244 246 / var(--tw-text-opacity, 1))}.text-gray-200{--tw-text-opacity: 1;color:rgb(229 231 235 / var(--tw-text-opacity, 1))}.text-gray-300{--tw-text-opacity: 1;color:rgb(209 213 219 / var(--tw-text-opacity, 1))}.text-gray-400{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.text-gray-500{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}.text-gray-600{--tw-text-opacity: 1;color:rgb(75 85 99 / var(--tw-text-opacity, 1))}.text-gray-700{--tw-text-opacity: 1;color:rgb(55 65 81 / var(--tw-text-opacity, 1))}.text-gray-800{--tw-text-opacity: 1;color:rgb(31 41 55 / var(--tw-text-opacity, 1))}.text-gray-900{--tw-text-opacity: 1;color:rgb(17 24 39 / var(--tw-text-opacity, 1))}.text-green-500{--tw-text-opacity: 1;color:rgb(34 197 94 / var(--tw-text-opacity, 1))}.text-green-600{--tw-text-opacity: 1;color:rgb(22 163 74 / var(--tw-text-opacity, 1))}.text-green-700{--tw-text-opacity: 1;color:rgb(21 128 61 / var(--tw-text-opacity, 1))}.text-green-800{--tw-text-opacity: 1;color:rgb(22 101 52 / var(--tw-text-opacity, 1))}.text-green-900{--tw-text-opacity: 1;color:rgb(20 83 45 / var(--tw-text-opacity, 1))}.text-orange-700{--tw-text-opacity: 1;color:rgb(194 65 12 / var(--tw-text-opacity, 1))}.text-primary-600{--tw-text-opacity: 1;color:rgb(2 132 199 / var(--tw-text-opacity, 1))}.text-primary-700{--tw-text-opacity: 1;color:rgb(3 105 161 / var(--tw-text-opacity, 1))}.text-primary-800{--tw-text-opacity: 1;color:rgb(7 89 133 / var(--tw-text-opacity, 1))}.text-purple-800{--tw-text-opacity: 1;color:rgb(107 33 168 / var(--tw-text-opacity, 1))}.text-red-200{--tw-text-opacity: 1;color:rgb(254 202 202 / var(--tw-text-opacity, 1))}.text-red-600{--tw-text-opacity: 1;color:rgb(220 38 38 / var(--tw-text-opacity, 1))}.text-red-700{--tw-text-opacity: 1;color:rgb(185 28 28 / var(--tw-text-opacity, 1))}.text-red-800{--tw-text-opacity: 1;color:rgb(153 27 27 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.text-yellow-600{--tw-text-opacity: 1;color:rgb(202 138 4 / var(--tw-text-opacity, 1))}.text-yellow-700{--tw-text-opacity: 1;color:rgb(161 98 7 / var(--tw-text-opacity, 1))}.text-yellow-800{--tw-text-opacity: 1;color:rgb(133 77 14 / var(--tw-text-opacity, 1))}.text-yellow-900{--tw-text-opacity: 1;color:rgb(113 63 18 / var(--tw-text-opacity, 1))}.underline{text-decoration-line:underline}.placeholder-gray-400::-moz-placeholder{--tw-placeholder-opacity: 1;color:rgb(156 163 175 / var(--tw-placeholder-opacity, 1))}.placeholder-gray-400::placeholder{--tw-placeholder-opacity: 1;color:rgb(156 163 175 / var(--tw-placeholder-opacity, 1))}.accent-primary-500{accent-color:#0ea5e9}.opacity-100{opacity:1}.opacity-25{opacity:.25}.opacity-50{opacity:.5}.opacity-60{opacity:.6}.opacity-75{opacity:.75}.shadow{--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-shadow{transition-property:box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}:root{font-family:Inter,system-ui,Avenir,Helvetica,Arial,sans-serif;line-height:1.5;font-weight:400;color-scheme:light dark;color:#ffffffde;background-color:#242424;font-synthesis:none;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body{margin:0;min-height:100vh}#root{min-height:100vh}@media(prefers-color-scheme:light){:root{color:#213547;background-color:#fff}}.hover\:border-gray-300:hover{--tw-border-opacity: 1;border-color:rgb(209 213 219 / var(--tw-border-opacity, 1))}.hover\:bg-gray-100:hover{--tw-bg-opacity: 1;background-color:rgb(243 244 246 / var(--tw-bg-opacity, 1))}.hover\:bg-gray-300:hover{--tw-bg-opacity: 1;background-color:rgb(209 213 219 / var(--tw-bg-opacity, 1))}.hover\:bg-gray-50:hover{--tw-bg-opacity: 1;background-color:rgb(249 250 251 / var(--tw-bg-opacity, 1))}.hover\:bg-primary-700:hover{--tw-bg-opacity: 1;background-color:rgb(3 105 161 / var(--tw-bg-opacity, 1))}.hover\:bg-red-100:hover{--tw-bg-opacity: 1;background-color:rgb(254 226 226 / var(--tw-bg-opacity, 1))}.hover\:bg-yellow-100:hover{--tw-bg-opacity: 1;background-color:rgb(254 249 195 / var(--tw-bg-opacity, 1))}.hover\:text-gray-500:hover{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}.hover\:text-gray-700:hover{--tw-text-opacity: 1;color:rgb(55 65 81 / var(--tw-text-opacity, 1))}.hover\:text-gray-900:hover{--tw-text-opacity: 1;color:rgb(17 24 39 / var(--tw-text-opacity, 1))}.hover\:text-primary-700:hover{--tw-text-opacity: 1;color:rgb(3 105 161 / var(--tw-text-opacity, 1))}.hover\:text-red-500:hover{--tw-text-opacity: 1;color:rgb(239 68 68 / var(--tw-text-opacity, 1))}.hover\:text-red-900:hover{--tw-text-opacity: 1;color:rgb(127 29 29 / var(--tw-text-opacity, 1))}.hover\:underline:hover{text-decoration-line:underline}.hover\:shadow-md:hover{--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.focus\:border-primary-500:focus{--tw-border-opacity: 1;border-color:rgb(14 165 233 / var(--tw-border-opacity, 1))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-0:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-2:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-primary-500:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(14 165 233 / var(--tw-ring-opacity, 1))}.focus\:ring-offset-2:focus{--tw-ring-offset-width: 2px}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-30:disabled{opacity:.3}.disabled\:opacity-50:disabled{opacity:.5}@media(min-width:640px){.sm\:ml-6{margin-left:1.5rem}.sm\:flex{display:flex}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.sm\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.sm\:space-x-8>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(2rem * var(--tw-space-x-reverse));margin-left:calc(2rem * calc(1 - var(--tw-space-x-reverse)))}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}}@media(min-width:768px){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}@media(min-width:1024px){.lg\:col-span-1{grid-column:span 1 / span 1}.lg\:col-span-4{grid-column:span 4 / span 4}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.lg\:px-8{padding-left:2rem;padding-right:2rem}}@media(prefers-color-scheme:dark){.dark\:divide-gray-700>:not([hidden])~:not([hidden]){--tw-divide-opacity: 1;border-color:rgb(55 65 81 / var(--tw-divide-opacity, 1))}.dark\:border-amber-800{--tw-border-opacity: 1;border-color:rgb(146 64 14 / var(--tw-border-opacity, 1))}.dark\:border-blue-800{--tw-border-opacity: 1;border-color:rgb(30 64 175 / var(--tw-border-opacity, 1))}.dark\:border-gray-600{--tw-border-opacity: 1;border-color:rgb(75 85 99 / var(--tw-border-opacity, 1))}.dark\:border-gray-700{--tw-border-opacity: 1;border-color:rgb(55 65 81 / var(--tw-border-opacity, 1))}.dark\:border-gray-700\/50{border-color:#37415180}.dark\:border-green-800{--tw-border-opacity: 1;border-color:rgb(22 101 52 / var(--tw-border-opacity, 1))}.dark\:border-orange-800{--tw-border-opacity: 1;border-color:rgb(154 52 18 / var(--tw-border-opacity, 1))}.dark\:border-primary-800{--tw-border-opacity: 1;border-color:rgb(7 89 133 / var(--tw-border-opacity, 1))}.dark\:border-red-800{--tw-border-opacity: 1;border-color:rgb(153 27 27 / var(--tw-border-opacity, 1))}.dark\:border-yellow-800{--tw-border-opacity: 1;border-color:rgb(133 77 14 / var(--tw-border-opacity, 1))}.dark\:bg-amber-900\/20{background-color:#78350f33}.dark\:bg-amber-900\/40{background-color:#78350f66}.dark\:bg-blue-900{--tw-bg-opacity: 1;background-color:rgb(30 58 138 / var(--tw-bg-opacity, 1))}.dark\:bg-blue-900\/20{background-color:#1e3a8a33}.dark\:bg-blue-900\/30{background-color:#1e3a8a4d}.dark\:bg-gray-600{--tw-bg-opacity: 1;background-color:rgb(75 85 99 / var(--tw-bg-opacity, 1))}.dark\:bg-gray-700{--tw-bg-opacity: 1;background-color:rgb(55 65 81 / var(--tw-bg-opacity, 1))}.dark\:bg-gray-700\/50{background-color:#37415180}.dark\:bg-gray-800{--tw-bg-opacity: 1;background-color:rgb(31 41 55 / var(--tw-bg-opacity, 1))}.dark\:bg-gray-900{--tw-bg-opacity: 1;background-color:rgb(17 24 39 / var(--tw-bg-opacity, 1))}.dark\:bg-green-500{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity, 1))}.dark\:bg-green-900{--tw-bg-opacity: 1;background-color:rgb(20 83 45 / var(--tw-bg-opacity, 1))}.dark\:bg-green-900\/20{background-color:#14532d33}.dark\:bg-green-900\/30{background-color:#14532d4d}.dark\:bg-orange-900\/20{background-color:#7c2d1233}.dark\:bg-primary-500{--tw-bg-opacity: 1;background-color:rgb(14 165 233 / var(--tw-bg-opacity, 1))}.dark\:bg-primary-800{--tw-bg-opacity: 1;background-color:rgb(7 89 133 / var(--tw-bg-opacity, 1))}.dark\:bg-primary-900{--tw-bg-opacity: 1;background-color:rgb(12 74 110 / var(--tw-bg-opacity, 1))}.dark\:bg-primary-900\/20{background-color:#0c4a6e33}.dark\:bg-primary-900\/30{background-color:#0c4a6e4d}.dark\:bg-purple-900{--tw-bg-opacity: 1;background-color:rgb(88 28 135 / var(--tw-bg-opacity, 1))}.dark\:bg-red-900{--tw-bg-opacity: 1;background-color:rgb(127 29 29 / var(--tw-bg-opacity, 1))}.dark\:bg-red-900\/20{background-color:#7f1d1d33}.dark\:bg-yellow-900{--tw-bg-opacity: 1;background-color:rgb(113 63 18 / var(--tw-bg-opacity, 1))}.dark\:bg-yellow-900\/20{background-color:#713f1233}.dark\:fill-gray-400{fill:#9ca3af}.dark\:fill-white{fill:#fff}.dark\:text-amber-200{--tw-text-opacity: 1;color:rgb(253 230 138 / var(--tw-text-opacity, 1))}.dark\:text-amber-300{--tw-text-opacity: 1;color:rgb(252 211 77 / var(--tw-text-opacity, 1))}.dark\:text-blue-200{--tw-text-opacity: 1;color:rgb(191 219 254 / var(--tw-text-opacity, 1))}.dark\:text-blue-300{--tw-text-opacity: 1;color:rgb(147 197 253 / var(--tw-text-opacity, 1))}.dark\:text-blue-400{--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity, 1))}.dark\:text-blue-500{--tw-text-opacity: 1;color:rgb(59 130 246 / var(--tw-text-opacity, 1))}.dark\:text-gray-200{--tw-text-opacity: 1;color:rgb(229 231 235 / var(--tw-text-opacity, 1))}.dark\:text-gray-300{--tw-text-opacity: 1;color:rgb(209 213 219 / var(--tw-text-opacity, 1))}.dark\:text-gray-400{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.dark\:text-gray-500{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}.dark\:text-gray-600{--tw-text-opacity: 1;color:rgb(75 85 99 / var(--tw-text-opacity, 1))}.dark\:text-gray-700{--tw-text-opacity: 1;color:rgb(55 65 81 / var(--tw-text-opacity, 1))}.dark\:text-green-200{--tw-text-opacity: 1;color:rgb(187 247 208 / var(--tw-text-opacity, 1))}.dark\:text-green-300{--tw-text-opacity: 1;color:rgb(134 239 172 / var(--tw-text-opacity, 1))}.dark\:text-green-400{--tw-text-opacity: 1;color:rgb(74 222 128 / var(--tw-text-opacity, 1))}.dark\:text-green-500{--tw-text-opacity: 1;color:rgb(34 197 94 / var(--tw-text-opacity, 1))}.dark\:text-orange-300{--tw-text-opacity: 1;color:rgb(253 186 116 / var(--tw-text-opacity, 1))}.dark\:text-primary-200{--tw-text-opacity: 1;color:rgb(186 230 253 / var(--tw-text-opacity, 1))}.dark\:text-primary-300{--tw-text-opacity: 1;color:rgb(125 211 252 / var(--tw-text-opacity, 1))}.dark\:text-primary-400{--tw-text-opacity: 1;color:rgb(56 189 248 / var(--tw-text-opacity, 1))}.dark\:text-purple-200{--tw-text-opacity: 1;color:rgb(233 213 255 / var(--tw-text-opacity, 1))}.dark\:text-purple-300{--tw-text-opacity: 1;color:rgb(216 180 254 / var(--tw-text-opacity, 1))}.dark\:text-red-200{--tw-text-opacity: 1;color:rgb(254 202 202 / var(--tw-text-opacity, 1))}.dark\:text-red-300{--tw-text-opacity: 1;color:rgb(252 165 165 / var(--tw-text-opacity, 1))}.dark\:text-red-400{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.dark\:text-red-500{--tw-text-opacity: 1;color:rgb(239 68 68 / var(--tw-text-opacity, 1))}.dark\:text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.dark\:text-yellow-200{--tw-text-opacity: 1;color:rgb(254 240 138 / var(--tw-text-opacity, 1))}.dark\:text-yellow-300{--tw-text-opacity: 1;color:rgb(253 224 71 / var(--tw-text-opacity, 1))}.dark\:text-yellow-400{--tw-text-opacity: 1;color:rgb(250 204 21 / var(--tw-text-opacity, 1))}.dark\:text-yellow-500{--tw-text-opacity: 1;color:rgb(234 179 8 / var(--tw-text-opacity, 1))}.dark\:hover\:border-gray-600:hover{--tw-border-opacity: 1;border-color:rgb(75 85 99 / var(--tw-border-opacity, 1))}.dark\:hover\:bg-gray-600:hover{--tw-bg-opacity: 1;background-color:rgb(75 85 99 / var(--tw-bg-opacity, 1))}.dark\:hover\:bg-gray-700:hover{--tw-bg-opacity: 1;background-color:rgb(55 65 81 / var(--tw-bg-opacity, 1))}.dark\:hover\:bg-gray-700\/50:hover{background-color:#37415180}.dark\:hover\:bg-red-900\/40:hover{background-color:#7f1d1d66}.dark\:hover\:bg-yellow-900\/40:hover{background-color:#713f1266}.dark\:hover\:text-gray-300:hover{--tw-text-opacity: 1;color:rgb(209 213 219 / var(--tw-text-opacity, 1))}.dark\:hover\:text-primary-300:hover{--tw-text-opacity: 1;color:rgb(125 211 252 / var(--tw-text-opacity, 1))}.dark\:hover\:text-red-300:hover{--tw-text-opacity: 1;color:rgb(252 165 165 / var(--tw-text-opacity, 1))}.dark\:hover\:text-white:hover{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}}