@parca/profile 0.19.23 → 0.19.24

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/CHANGELOG.md CHANGED
@@ -3,6 +3,10 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [0.19.24](https://github.com/parca-dev/parca/compare/@parca/profile@0.19.23...@parca/profile@0.19.24) (2025-07-17)
7
+
8
+ **Note:** Version bump only for package @parca/profile
9
+
6
10
  ## [0.19.23](https://github.com/parca-dev/parca/compare/@parca/profile@0.19.22...@parca/profile@0.19.23) (2025-07-16)
7
11
 
8
12
  **Note:** Version bump only for package @parca/profile
@@ -0,0 +1,11 @@
1
+ import type { ProfileFilter } from '@parca/store';
2
+ export interface FilterPreset {
3
+ key: string;
4
+ name: string;
5
+ description: string;
6
+ filters: Array<Omit<ProfileFilter, 'id'>>;
7
+ }
8
+ export declare const filterPresets: FilterPreset[];
9
+ export declare const isPresetKey: (key: string) => boolean;
10
+ export declare const getPresetByKey: (key: string) => FilterPreset | undefined;
11
+ //# sourceMappingURL=filterPresets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filterPresets.d.ts","sourceRoot":"","sources":["../../../../src/ProfileView/components/ProfileFilters/filterPresets.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,cAAc,CAAC;AAEhD,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC,CAAC;CAC3C;AAED,eAAO,MAAM,aAAa,EAAE,YAAY,EA6CvC,CAAC;AAEF,eAAO,MAAM,WAAW,GAAI,KAAK,MAAM,KAAG,OAEzC,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,KAAK,MAAM,KAAG,YAAY,GAAG,SAE3D,CAAC"}
@@ -0,0 +1,64 @@
1
+ // Copyright 2022 The Parca Authors
2
+ // Licensed under the Apache License, Version 2.0 (the "License");
3
+ // you may not use this file except in compliance with the License.
4
+ // You may obtain a copy of the License at
5
+ //
6
+ // http://www.apache.org/licenses/LICENSE-2.0
7
+ //
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+ export const filterPresets = [
14
+ {
15
+ key: 'go_runtime_expected_off_cpu',
16
+ name: 'Go Runtime Expected Off-CPU',
17
+ description: 'Excludes expected Go runtime blocking functions',
18
+ filters: [
19
+ {
20
+ type: 'stack',
21
+ field: 'function_name',
22
+ matchType: 'not_equal',
23
+ value: 'runtime.usleep',
24
+ },
25
+ {
26
+ type: 'stack',
27
+ field: 'function_name',
28
+ matchType: 'not_equal',
29
+ value: 'runtime.futex',
30
+ },
31
+ ],
32
+ },
33
+ {
34
+ key: 'rust_runtime_expected_off_cpu',
35
+ name: 'Rust Expected Off-CPU',
36
+ description: 'Excludes expected Rust runtime blocking functions',
37
+ filters: [
38
+ {
39
+ type: 'stack',
40
+ field: 'function_name',
41
+ matchType: 'not_equal',
42
+ value: 'parking_lot_core::thread_parker::imp::ThreadParker::futex_wait',
43
+ },
44
+ {
45
+ type: 'stack',
46
+ field: 'function_name',
47
+ matchType: 'not_equal',
48
+ value: 'tokio::runtime::time::Driver::park_internal',
49
+ },
50
+ {
51
+ type: 'stack',
52
+ field: 'function_name',
53
+ matchType: 'not_equal',
54
+ value: 'futex_wait',
55
+ },
56
+ ],
57
+ },
58
+ ];
59
+ export const isPresetKey = (key) => {
60
+ return filterPresets.some(preset => preset.key === key);
61
+ };
62
+ export const getPresetByKey = (key) => {
63
+ return filterPresets.find(preset => preset.key === key);
64
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/ProfileView/components/ProfileFilters/index.tsx"],"names":[],"mappings":"AAoBA,OAAO,EAAoB,KAAK,aAAa,EAAC,MAAM,qBAAqB,CAAC;AAE1E,eAAO,MAAM,gBAAgB,GAAI,QAAQ,aAAa,KAAG,OAIxD,CAAC;AA4HF,QAAA,MAAM,cAAc,QAAO,GAAG,CAAC,OA6I9B,CAAC;AAEF,eAAe,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/ProfileView/components/ProfileFilters/index.tsx"],"names":[],"mappings":"AAqBA,OAAO,EAAoB,KAAK,aAAa,EAAC,MAAM,qBAAqB,CAAC;AAE1E,eAAO,MAAM,gBAAgB,GAAI,QAAQ,aAAa,KAAG,OASxD,CAAC;AAyIF,QAAA,MAAM,cAAc,QAAO,GAAG,CAAC,OA+K9B,CAAC;AAEF,eAAe,cAAc,CAAC"}
@@ -15,8 +15,14 @@ import { useCallback } from 'react';
15
15
  import { Icon } from '@iconify/react';
16
16
  import cx from 'classnames';
17
17
  import { Button, Input, Select } from '@parca/components';
18
+ import { filterPresets, getPresetByKey, isPresetKey } from './filterPresets';
18
19
  import { useProfileFilters } from './useProfileFilters';
19
20
  export const isFilterComplete = (filter) => {
21
+ // For preset filters, only need type and value
22
+ if (filter.type != null && isPresetKey(filter.type)) {
23
+ return filter.value !== '' && filter.type != null;
24
+ }
25
+ // For regular filters, need all fields
20
26
  return (filter.value !== '' && filter.type != null && filter.field != null && filter.matchType != null);
21
27
  };
22
28
  const filterTypeItems = [
@@ -34,6 +40,13 @@ const filterTypeItems = [
34
40
  expanded: (_jsxs(_Fragment, { children: [_jsx("span", { children: "Frame Filter" }), _jsx("br", {}), _jsx("span", { className: "text-xs", children: "Filters individual frames" })] })),
35
41
  },
36
42
  },
43
+ ...filterPresets.map(preset => ({
44
+ key: preset.key,
45
+ element: {
46
+ active: _jsx(_Fragment, { children: preset.name }),
47
+ expanded: (_jsxs(_Fragment, { children: [_jsx("span", { children: preset.name }), _jsx("br", {}), _jsx("span", { className: "text-xs", children: preset.description })] })),
48
+ },
49
+ })),
37
50
  ];
38
51
  const fieldItems = [
39
52
  {
@@ -140,14 +153,40 @@ const ProfileFilters = () => {
140
153
  return (_jsxs("div", { className: "flex gap-2 w-full", children: [_jsxs("div", { className: "flex-1 flex flex-wrap gap-2", children: [filtersToRender.map(filter => {
141
154
  const isNumberField = filter.field === 'address' || filter.field === 'line_number';
142
155
  const matchTypeItems = isNumberField ? numberMatchTypeItems : stringMatchTypeItems;
156
+ const isPresetFilter = filter.type != null && isPresetKey(filter.type);
143
157
  return (_jsxs("div", { className: "flex items-center gap-0", children: [_jsx(Select, { items: filterTypeItems, selectedKey: filter.type, placeholder: "Select Filter", onSelection: key => {
144
- const newType = key;
145
- updateFilter(filter.id, {
146
- type: newType,
147
- field: filter.field ?? 'function_name',
148
- matchType: filter.matchType ?? 'contains',
149
- });
150
- }, className: cx('rounded-l-md pr-1 gap-0 focus:z-50 focus:relative focus:outline-1 rounded-r-none ', filter.type != null ? 'border-r-0 w-28' : 'w-32') }), filter.type != null && (_jsxs(_Fragment, { children: [_jsx(Select, { items: fieldItems, selectedKey: filter.field ?? '', onSelection: key => {
158
+ // Check if this is a preset selection
159
+ if (isPresetKey(key)) {
160
+ const preset = getPresetByKey(key);
161
+ if (preset != null) {
162
+ updateFilter(filter.id, {
163
+ type: preset.key,
164
+ field: undefined,
165
+ matchType: undefined,
166
+ value: preset.name,
167
+ });
168
+ }
169
+ }
170
+ else {
171
+ const newType = key;
172
+ // Check if we're converting a preset filter to a regular filter
173
+ if (filter.type != null && isPresetKey(filter.type)) {
174
+ updateFilter(filter.id, {
175
+ type: newType,
176
+ field: 'function_name',
177
+ matchType: 'contains',
178
+ value: '',
179
+ });
180
+ }
181
+ else {
182
+ updateFilter(filter.id, {
183
+ type: newType,
184
+ field: filter.field ?? 'function_name',
185
+ matchType: filter.matchType ?? 'contains',
186
+ });
187
+ }
188
+ }
189
+ }, className: cx('rounded-l-md pr-1 gap-0 focus:z-50 focus:relative focus:outline-1', isPresetFilter ? 'rounded-r-none border-r-0' : 'rounded-r-none', filter.type != null ? 'border-r-0 w-auto' : 'w-32') }), filter.type != null && !isPresetFilter && (_jsxs(_Fragment, { children: [_jsx(Select, { items: fieldItems, selectedKey: filter.field ?? '', onSelection: key => {
151
190
  const newField = key;
152
191
  const isNewFieldNumber = newField === 'address' || newField === 'line_number';
153
192
  const isCurrentFieldNumber = filter.field === 'address' || filter.field === 'line_number';
@@ -161,13 +200,19 @@ const ProfileFilters = () => {
161
200
  updateFilter(filter.id, { field: newField });
162
201
  }
163
202
  }, className: "rounded-none border-r-0 w-32 pr-1 gap-0 focus:z-50 focus:relative focus:outline-1" }), _jsx(Select, { items: matchTypeItems, selectedKey: filter.matchType ?? '', onSelection: key => updateFilter(filter.id, { matchType: key }), className: "rounded-none border-r-0 pr-1 gap-0 focus:z-50 focus:relative focus:outline-1" }), _jsx(Input, { placeholder: "Value", value: filter.value, onChange: e => updateFilter(filter.id, { value: e.target.value }), onKeyDown: handleKeyDown, className: "rounded-none w-36 text-sm focus:outline-1" })] })), _jsx(Button, { variant: "neutral", onClick: () => {
164
- if (localFilters.length === 1) {
203
+ // If we're displaying local filters and this is the last one, reset everything
204
+ if (localFilters.length > 0 && localFilters.length === 1) {
205
+ resetFilters();
206
+ }
207
+ // If we're displaying applied filters and this is the last one, reset everything
208
+ else if (localFilters.length === 0 && filtersToRender.length === 1) {
165
209
  resetFilters();
166
210
  }
211
+ // Otherwise, just remove this specific filter
167
212
  else {
168
213
  removeFilter(filter.id);
169
214
  }
170
215
  }, className: cx('h-[38px] p-3', filter.type != null ? 'rounded-none rounded-r-md' : 'rounded-l-none rounded-r-md'), children: _jsx(Icon, { icon: "mdi:close", className: "h-4 w-4" }) })] }, filter.id));
171
- }), localFilters.length > 0 && (_jsx(Button, { variant: "neutral", onClick: addFilter, className: "p-3 h-[38px]", children: _jsx(Icon, { icon: "mdi:filter-plus-outline", className: "h-4 w-4" }) })), localFilters.length === 0 && (appliedFilters?.length ?? 0) === 0 && (_jsxs(Button, { variant: "neutral", onClick: addFilter, className: "flex items-center gap-2", children: [_jsx(Icon, { icon: "mdi:filter-outline", className: "h-4 w-4" }), _jsx("span", { children: "Filter" })] }))] }), localFilters.length > 0 && hasUnsavedChanges && localFilters.some(isFilterComplete) && (_jsx(Button, { variant: "primary", onClick: onApplyFilters, className: cx('flex items-center gap-2 self-end'), children: _jsx("span", { children: "Apply" }) }))] }));
216
+ }), localFilters.length > 0 && (_jsx(Button, { variant: "neutral", onClick: addFilter, className: "p-3 h-[38px]", children: _jsx(Icon, { icon: "mdi:filter-plus-outline", className: "h-4 w-4" }) })), localFilters.length === 0 && (appliedFilters?.length ?? 0) === 0 && (_jsxs(Button, { variant: "neutral", onClick: addFilter, className: "flex items-center gap-2", children: [_jsx(Icon, { icon: "mdi:filter-outline", className: "h-4 w-4" }), _jsx("span", { children: "Filter" })] }))] }), localFilters.length > 0 && (_jsx(Button, { variant: "primary", onClick: onApplyFilters, disabled: !hasUnsavedChanges || !localFilters.some(isFilterComplete), className: cx('flex items-center gap-2 self-end'), children: _jsx("span", { children: "Apply" }) }))] }));
172
217
  };
173
218
  export default ProfileFilters;
@@ -1,5 +1,6 @@
1
1
  import { type Filter } from '@parca/client';
2
2
  import { type ProfileFilter } from '@parca/store';
3
+ import { type FilterPreset } from './filterPresets';
3
4
  export type { ProfileFilter };
4
5
  export declare const convertToProtoFilters: (profileFilters: ProfileFilter[]) => Filter[];
5
6
  export declare const useProfileFilters: () => {
@@ -14,5 +15,6 @@ export declare const useProfileFilters: () => {
14
15
  removeFilter: (id: string) => void;
15
16
  updateFilter: (id: string, updates: Partial<ProfileFilter>) => void;
16
17
  resetFilters: () => void;
18
+ applyPreset: (preset: FilterPreset) => void;
17
19
  };
18
20
  //# sourceMappingURL=useProfileFilters.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useProfileFilters.d.ts","sourceRoot":"","sources":["../../../../src/ProfileView/components/ProfileFilters/useProfileFilters.ts"],"names":[],"mappings":"AAeA,OAAO,EAAC,KAAK,MAAM,EAAC,MAAM,eAAe,CAAC;AAC1C,OAAO,EAKL,KAAK,aAAa,EACnB,MAAM,cAAc,CAAC;AAItB,YAAY,EAAC,aAAa,EAAC,CAAC;AAG5B,eAAO,MAAM,qBAAqB,GAAI,gBAAgB,aAAa,EAAE,KAAG,MAAM,EA+E7E,CAAC;AAEF,eAAO,MAAM,iBAAiB,QAAO;IACnC,YAAY,EAAE,aAAa,EAAE,CAAC;IAC9B,cAAc,EAAE,aAAa,EAAE,CAAC;IAChC,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,aAAa,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,mBAAmB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAClD,YAAY,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,YAAY,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,aAAa,CAAC,KAAK,IAAI,CAAC;IACpE,YAAY,EAAE,MAAM,IAAI,CAAC;CAkK1B,CAAC"}
1
+ {"version":3,"file":"useProfileFilters.d.ts","sourceRoot":"","sources":["../../../../src/ProfileView/components/ProfileFilters/useProfileFilters.ts"],"names":[],"mappings":"AAeA,OAAO,EAAC,KAAK,MAAM,EAAC,MAAM,eAAe,CAAC;AAC1C,OAAO,EAKL,KAAK,aAAa,EACnB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAA8B,KAAK,YAAY,EAAC,MAAM,iBAAiB,CAAC;AAG/E,YAAY,EAAC,aAAa,EAAC,CAAC;AAG5B,eAAO,MAAM,qBAAqB,GAAI,gBAAgB,aAAa,EAAE,KAAG,MAAM,EAoG7E,CAAC;AAEF,eAAO,MAAM,iBAAiB,QAAO;IACnC,YAAY,EAAE,aAAa,EAAE,CAAC;IAC9B,cAAc,EAAE,aAAa,EAAE,CAAC;IAChC,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,aAAa,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,mBAAmB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAClD,YAAY,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,YAAY,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,aAAa,CAAC,KAAK,IAAI,CAAC;IACpE,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,WAAW,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,IAAI,CAAC;CAwM7C,CAAC"}
@@ -12,10 +12,31 @@
12
12
  // limitations under the License.
13
13
  import { useCallback, useEffect, useMemo } from 'react';
14
14
  import { selectLocalFilters, setLocalFilters, useAppDispatch, useAppSelector, } from '@parca/store';
15
+ import { getPresetByKey, isPresetKey } from './filterPresets';
15
16
  import { useProfileFiltersUrlState } from './useProfileFiltersUrlState';
16
17
  // Convert ProfileFilter[] to protobuf Filter[] matching the expected structure
17
18
  export const convertToProtoFilters = (profileFilters) => {
18
- return profileFilters
19
+ // First, expand any preset filters to their constituent filters
20
+ const expandedFilters = [];
21
+ for (const filter of profileFilters) {
22
+ if (filter.type != null && isPresetKey(filter.type)) {
23
+ // This is a preset filter, expand it
24
+ const preset = getPresetByKey(filter.type);
25
+ if (preset != null) {
26
+ preset.filters.forEach((presetFilter, index) => {
27
+ expandedFilters.push({
28
+ ...presetFilter,
29
+ id: `${filter.id}-expanded-${index}`,
30
+ });
31
+ });
32
+ }
33
+ }
34
+ else {
35
+ // Regular filter, add as is
36
+ expandedFilters.push(filter);
37
+ }
38
+ }
39
+ return expandedFilters
19
40
  .filter(f => f.value !== '' && f.type != null && f.field != null && f.matchType != null) // Only include complete filters with values
20
41
  .map(f => {
21
42
  // Build the condition based on field type
@@ -117,8 +138,22 @@ export const useProfileFilters = () => {
117
138
  // eslint-disable-next-line react-hooks/exhaustive-deps
118
139
  }, []);
119
140
  const hasUnsavedChanges = useMemo(() => {
120
- const localWithValues = localFilters.filter(f => f.value !== '');
121
- const appliedWithValues = (appliedFilters ?? []).filter(f => f.value !== '');
141
+ const localWithValues = localFilters.filter(f => {
142
+ // For preset filters, only need type and value
143
+ if (f.type != null && isPresetKey(f.type)) {
144
+ return f.value !== '' && f.type != null;
145
+ }
146
+ // For regular filters, need all fields
147
+ return f.value !== '' && f.type != null && f.field != null && f.matchType != null;
148
+ });
149
+ const appliedWithValues = (appliedFilters ?? []).filter(f => {
150
+ // For preset filters, only need type and value
151
+ if (f.type != null && isPresetKey(f.type)) {
152
+ return f.value !== '' && f.type != null;
153
+ }
154
+ // For regular filters, need all fields
155
+ return f.value !== '' && f.type != null && f.field != null && f.matchType != null;
156
+ });
122
157
  if (localWithValues.length !== appliedWithValues.length)
123
158
  return true;
124
159
  return !localWithValues.every((local, index) => {
@@ -183,7 +218,14 @@ export const useProfileFilters = () => {
183
218
  setAppliedFilters([]);
184
219
  }, [dispatch, setAppliedFilters]);
185
220
  const onApplyFilters = useCallback(() => {
186
- const validFilters = localFilters.filter(f => f.value !== '' && f.type != null && f.field != null && f.matchType != null);
221
+ const validFilters = localFilters.filter(f => {
222
+ // For preset filters, only need type and value
223
+ if (f.type != null && isPresetKey(f.type)) {
224
+ return f.value !== '' && f.type != null;
225
+ }
226
+ // For regular filters, need all fields
227
+ return f.value !== '' && f.type != null && f.field != null && f.matchType != null;
228
+ });
187
229
  const filtersToApply = validFilters.map((f, index) => ({
188
230
  ...f,
189
231
  id: `filter-${Date.now()}-${index}`,
@@ -193,6 +235,17 @@ export const useProfileFilters = () => {
193
235
  const protoFilters = useMemo(() => {
194
236
  return convertToProtoFilters(appliedFilters ?? []);
195
237
  }, [appliedFilters]);
238
+ const applyPreset = useCallback((preset) => {
239
+ const presetFilter = {
240
+ id: `filter-preset-${Date.now()}`,
241
+ type: preset.key,
242
+ value: preset.name,
243
+ };
244
+ // Add preset filter to existing filters
245
+ const updatedFilters = [...localFilters, presetFilter];
246
+ dispatch(setLocalFilters(updatedFilters));
247
+ setAppliedFilters(updatedFilters);
248
+ }, [dispatch, setAppliedFilters, localFilters]);
196
249
  return {
197
250
  localFilters,
198
251
  appliedFilters,
@@ -205,5 +258,6 @@ export const useProfileFilters = () => {
205
258
  removeFilter,
206
259
  updateFilter,
207
260
  resetFilters,
261
+ applyPreset,
208
262
  };
209
263
  };
@@ -1 +1 @@
1
- {"version":3,"file":"useProfileFiltersUrlState.d.ts","sourceRoot":"","sources":["../../../../src/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.ts"],"names":[],"mappings":"AAaA,OAAO,EAAoB,KAAK,sBAAsB,EAAC,MAAM,mBAAmB,CAAC;AACjF,OAAO,EAAC,KAAK,aAAa,EAAC,MAAM,cAAc,CAAC;AA8ChD,eAAO,MAAM,oBAAoB,GAAI,SAAS,MAAM,KAAG,aAAa,EAmBnE,CAAC;AAEF,eAAO,MAAM,yBAAyB,QAAO;IAC3C,cAAc,EAAE,aAAa,EAAE,CAAC;IAChC,iBAAiB,EAAE,sBAAsB,CAAC,aAAa,EAAE,CAAC,CAAC;CAmB5D,CAAC"}
1
+ {"version":3,"file":"useProfileFiltersUrlState.d.ts","sourceRoot":"","sources":["../../../../src/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.ts"],"names":[],"mappings":"AAaA,OAAO,EAAoB,KAAK,sBAAsB,EAAC,MAAM,mBAAmB,CAAC;AACjF,OAAO,EAAC,KAAK,aAAa,EAAC,MAAM,cAAc,CAAC;AAwDhD,eAAO,MAAM,oBAAoB,GAAI,SAAS,MAAM,KAAG,aAAa,EAkCnE,CAAC;AAEF,eAAO,MAAM,yBAAyB,QAAO;IAC3C,cAAc,EAAE,aAAa,EAAE,CAAC;IAChC,iBAAiB,EAAE,sBAAsB,CAAC,aAAa,EAAE,CAAC,CAAC;CAmB5D,CAAC"}
@@ -11,6 +11,7 @@
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
13
  import { useURLStateCustom } from '@parca/components';
14
+ import { isPresetKey } from './filterPresets';
14
15
  // Compact encoding mappings
15
16
  const TYPE_MAP = {
16
17
  stack: 's',
@@ -39,8 +40,15 @@ const encodeProfileFilters = (filters) => {
39
40
  if (filters.length === 0)
40
41
  return '';
41
42
  return filters
42
- .filter(f => f.value !== '' && f.type != null && f.field != null && f.matchType != null)
43
+ .filter(f => f.value !== '' && f.type != null)
43
44
  .map(f => {
45
+ // Handle preset filters differently
46
+ if (isPresetKey(f.type)) {
47
+ const presetKey = encodeURIComponent(f.type);
48
+ const value = encodeURIComponent(f.value);
49
+ return `p:${presetKey}:${value}`;
50
+ }
51
+ // Handle regular filters
44
52
  const type = TYPE_MAP[f.type];
45
53
  const field = FIELD_MAP[f.field];
46
54
  const match = MATCH_MAP[f.matchType];
@@ -55,7 +63,19 @@ export const decodeProfileFilters = (encoded) => {
55
63
  return [];
56
64
  try {
57
65
  return encoded.split(',').map((filter, index) => {
58
- const [type, field, match, ...valueParts] = filter.split(':');
66
+ const parts = filter.split(':');
67
+ // Handle preset filters (format: p:presetKey:value)
68
+ if (parts[0] === 'p' && parts.length >= 3) {
69
+ const presetKey = decodeURIComponent(parts[1]);
70
+ const value = decodeURIComponent(parts.slice(2).join(':')); // Handle values with colons
71
+ return {
72
+ id: `filter-${Date.now()}-${index}`,
73
+ type: presetKey,
74
+ value,
75
+ };
76
+ }
77
+ // Handle regular filters (format: type:field:match:value)
78
+ const [type, field, match, ...valueParts] = parts;
59
79
  const value = decodeURIComponent(valueParts.join(':')); // Handle values with colons
60
80
  return {
61
81
  id: `filter-${Date.now()}-${index}`,
package/dist/styles.css CHANGED
@@ -1 +1 @@
1
- /*! tailwindcss v3.2.4 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}html{-webkit-text-size-adjust:100%;font-feature-settings:normal;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}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,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;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{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-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,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]{display:none}*,:after,:before{--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-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:rgba(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: }::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-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:rgba(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: }.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.sr-only{clip:rect(0,0,0,0);border-width:0;height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.-inset-2{bottom:-.5rem;left:-.5rem;right:-.5rem;top:-.5rem}.inset-y-0{bottom:0;top:0}.right-0{right:0}.top-0{top:0}.left-0{left:0}.bottom-3{bottom:.75rem}.top-\[-5px\]{top:-5px}.top-\[-2px\]{top:-2px}.bottom-0{bottom:0}.left-\[-7px\]{left:-7px}.right-\[-7px\]{right:-7px}.right-8{right:2rem}.top-\[-46px\]{top:-46px}.right-full{right:100%}.left-full{left:100%}.top-\[-60px\]{top:-60px}.left-1\/2{left:50%}.top-full{top:100%}.z-10{z-index:10}.z-50{z-index:50}.z-30{z-index:30}.z-\[5\]{z-index:5}.z-20{z-index:20}.m-auto{margin:auto}.m-2{margin:.5rem}.mx-2{margin-left:.5rem;margin-right:.5rem}.my-2{margin-bottom:.5rem;margin-top:.5rem}.my-0{margin-bottom:0;margin-top:0}.my-20{margin-bottom:5rem;margin-top:5rem}.mx-auto{margin-left:auto;margin-right:auto}.my-4{margin-bottom:1rem;margin-top:1rem}.mr-3{margin-right:.75rem}.mt-1{margin-top:.25rem}.ml-\[70px\]{margin-left:70px}.mb-2{margin-bottom:.5rem}.ml-auto{margin-left:auto}.mt-auto{margin-top:auto}.mb-0\.5{margin-bottom:.125rem}.mt-1\.5{margin-top:.375rem}.mb-0{margin-bottom:0}.mr-2{margin-right:.5rem}.ml-3{margin-left:.75rem}.mt-2{margin-top:.5rem}.mb-px{margin-bottom:1px}.ml-2{margin-left:.5rem}.mr-6{margin-right:1.5rem}.mb-1{margin-bottom:.25rem}.mr-1{margin-right:.25rem}.mt-4{margin-top:1rem}.mb-4{margin-bottom:1rem}.mt-3{margin-top:.75rem}.mt-8{margin-top:2rem}.mt-0{margin-top:0}.ml-1{margin-left:.25rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.\!flex{display:flex!important}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-10{height:2.5rem}.h-fit{height:-moz-fit-content;height:fit-content}.h-\[38px\]{height:38px}.h-auto{height:auto}.h-full{height:100%}.h-1{height:.25rem}.h-\[20px\]{height:20px}.h-\[16px\]{height:16px}.h-4{height:1rem}.h-\[30px\]{height:30px}.h-\[45px\]{height:45px}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-\[700px\]{height:700px}.h-\[80vh\]{height:80vh}.h-\[1px\]{height:1px}.h-\[14px\]{height:14px}.h-\[24px\]{height:24px}.h-0{height:0}.max-h-\[400px\]{max-height:400px}.max-h-\[50vh\]{max-height:50vh}.max-h-\[300px\]{max-height:300px}.min-h-52{min-height:13rem}.min-h-5{min-height:1.25rem}.min-h-48{min-height:12rem}.min-h-\[50px\]{min-height:50px}.min-h-\[700px\]{min-height:700px}.min-h-96{min-height:24rem}.w-full{width:100%}.w-\[500px\]{width:500px}.w-1\/4{width:25%}.w-3\/4{width:75%}.w-max{width:-moz-max-content;width:max-content}.w-40{width:10rem}.w-\[44px\]{width:44px}.w-\[16px\]{width:16px}.w-auto{width:auto}.w-\[270px\]{width:270px}.w-\[300px\]{width:300px}.w-5{width:1.25rem}.w-3{width:.75rem}.w-7{width:1.75rem}.w-9{width:2.25rem}.w-11{width:2.75rem}.w-\[52px\]{width:52px}.w-\[68px\]{width:68px}.w-\[76px\]{width:76px}.w-\[84px\]{width:84px}.w-\[92px\]{width:92px}.w-\[100px\]{width:100px}.w-\[108px\]{width:108px}.w-\[116px\]{width:116px}.w-4{width:1rem}.w-56{width:14rem}.w-\[350px\]{width:350px}.w-\[260px\]{width:260px}.w-44{width:11rem}.w-\[460px\]{width:460px}.w-\[19\.25\%\]{width:19.25%}.w-11\/12{width:91.666667%}.w-1\/12{width:8.333333%}.w-8{width:2rem}.w-\[50\%\]{width:50%}.w-28{width:7rem}.w-32{width:8rem}.w-36{width:9rem}.w-16{width:4rem}.w-fit{width:-moz-fit-content;width:fit-content}.w-\[420px\]{width:420px}.w-48{width:12rem}.w-\[14px\]{width:14px}.w-64{width:16rem}.w-80{width:20rem}.w-\[20px\]{width:20px}.w-\[246px\]{width:246px}.w-52{width:13rem}.w-0{width:0}.min-w-\[300px\]{min-width:300px}.min-w-\[400px\]{min-width:400px}.min-w-\[350px\]{min-width:350px}.min-w-\[260px\]{min-width:260px}.max-w-\[500px\]{max-width:500px}.max-w-md{max-width:28rem}.max-w-80{max-width:20rem}.max-w-full{max-width:100%}.max-w-48{max-width:12rem}.max-w-\[300px\]{max-width:300px}.max-w-\[400px\]{max-width:400px}.max-w-lg{max-width:32rem}.flex-1{flex:1 1 0%}.flex-shrink-0,.shrink-0{flex-shrink:0}.shrink{flex-shrink:1}.flex-grow-0{flex-grow:0}.flex-grow{flex-grow:1}.table-auto{table-layout:auto}.table-fixed{table-layout:fixed}.origin-top-right{transform-origin:top right}.origin-top-left{transform-origin:top left}.translate-x-6{--tw-translate-x:1.5rem}.translate-x-0,.translate-x-6{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-0{--tw-translate-x:0px}.translate-y-1{--tw-translate-y:0.25rem}.translate-y-0,.translate-y-1{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-0{--tw-translate-y:0px}.translate-x-5{--tw-translate-x:1.25rem}.-translate-x-1\/2,.translate-x-5{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-x-1\/2{--tw-translate-x:-50%}.rotate-90{--tw-rotate:90deg}.-rotate-180,.rotate-90{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-rotate-180{--tw-rotate:-180deg}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-default{cursor:default}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.cursor-ew-resize{cursor:ew-resize}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize{resize:both}.list-inside{list-style-position:inside}.list-disc{list-style-type:disc}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-row{flex-direction:row}.flex-col{flex-direction:column}.flex-col-reverse{flex-direction:column-reverse}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-4{gap:1rem}.gap-1{gap:.25rem}.gap-\[2px\]{gap:2px}.gap-2{gap:.5rem}.gap-6{gap:1.5rem}.gap-3{gap:.75rem}.gap-0{gap:0}.gap-x-2{-moz-column-gap:.5rem;column-gap:.5rem}.space-y-5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(1.25rem*var(--tw-space-y-reverse));margin-top:calc(1.25rem*(1 - var(--tw-space-y-reverse)))}.self-end{align-self:flex-end}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-clip{overflow:clip}.overflow-scroll{overflow:scroll}.overflow-x-hidden{overflow-x:hidden}.text-ellipsis{text-overflow:ellipsis}.whitespace-normal{white-space:normal}.whitespace-nowrap{white-space:nowrap}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-sm{border-radius:.125rem}.rounded-full{border-radius:9999px}.rounded-none{border-radius:0}.rounded-\[4px\]{border-radius:4px}.rounded-l-md{border-bottom-left-radius:.375rem;border-top-left-radius:.375rem}.rounded-l-none{border-bottom-left-radius:0;border-top-left-radius:0}.rounded-r-none{border-bottom-right-radius:0;border-top-right-radius:0}.rounded-r-md{border-bottom-right-radius:.375rem;border-top-right-radius:.375rem}.rounded-t-lg{border-top-left-radius:.5rem;border-top-right-radius:.5rem}.rounded-b{border-bottom-right-radius:.25rem}.rounded-b,.rounded-l{border-bottom-left-radius:.25rem}.rounded-l{border-top-left-radius:.25rem}.rounded-r{border-bottom-right-radius:.25rem;border-top-right-radius:.25rem}.rounded-tr-none{border-top-right-radius:0}.rounded-br-none{border-bottom-right-radius:0}.rounded-tl-none{border-top-left-radius:0}.rounded-bl-none{border-bottom-left-radius:0}.border{border-width:1px}.border-2{border-width:2px}.border-x-2{border-left-width:2px;border-right-width:2px}.border-b{border-bottom-width:1px}.border-t{border-top-width:1px}.border-r{border-right-width:1px}.border-l{border-left-width:1px}.border-l-0{border-left-width:0}.border-r-0{border-right-width:0}.border-l-4{border-left-width:4px}.border-r-4{border-right-width:4px}.border-t-4{border-top-width:4px}.border-none{border-style:none}.border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity))}.border-red-400{--tw-border-opacity:1;border-color:rgb(248 113 113/var(--tw-border-opacity))}.border-transparent{border-color:transparent}.border-indigo-500{--tw-border-opacity:1;border-color:rgb(99 102 241/var(--tw-border-opacity))}.border-gray-200{--tw-border-opacity:1;border-color:rgb(229 231 235/var(--tw-border-opacity))}.border-gray-400{--tw-border-opacity:1;border-color:rgb(156 163 175/var(--tw-border-opacity))}.border-gray-900{--tw-border-opacity:1;border-color:rgb(17 24 39/var(--tw-border-opacity))}.border-gray-100{--tw-border-opacity:1;border-color:rgb(243 244 246/var(--tw-border-opacity))}.border-r-gray-200{--tw-border-opacity:1;border-right-color:rgb(229 231 235/var(--tw-border-opacity))}.border-l-amber-900{--tw-border-opacity:1;border-left-color:rgb(120 53 15/var(--tw-border-opacity))}.border-l-transparent{border-left-color:transparent}.border-r-transparent{border-right-color:transparent}.bg-gray-200{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.bg-indigo-600{--tw-bg-opacity:1;background-color:rgb(79 70 229/var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-white\/50{background-color:hsla(0,0%,100%,.5)}.bg-red-100{--tw-bg-opacity:1;background-color:rgb(254 226 226/var(--tw-bg-opacity))}.bg-black{--tw-bg-opacity:1;background-color:rgb(0 0 0/var(--tw-bg-opacity))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity))}.bg-gray-400{--tw-bg-opacity:1;background-color:rgb(156 163 175/var(--tw-bg-opacity))}.bg-indigo-100{--tw-bg-opacity:1;background-color:rgb(224 231 255/var(--tw-bg-opacity))}.bg-yellow-200{--tw-bg-opacity:1;background-color:rgb(254 240 138/var(--tw-bg-opacity))}.bg-yellow-700{--tw-bg-opacity:1;background-color:rgb(161 98 7/var(--tw-bg-opacity))}.bg-gray-500{--tw-bg-opacity:1;background-color:rgb(107 114 128/var(--tw-bg-opacity))}.bg-gray-500\/50{background-color:hsla(220,9%,46%,.5)}.bg-gray-700\/75{background-color:rgba(55,65,81,.75)}.bg-gray-800{--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}.bg-gray-600{--tw-bg-opacity:1;background-color:rgb(75 85 99/var(--tw-bg-opacity))}.bg-indigo-50{--tw-bg-opacity:1;background-color:rgb(238 242 255/var(--tw-bg-opacity))}.bg-gray-700{--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}.bg-inherit{background-color:inherit}.bg-indigo-400{--tw-bg-opacity:1;background-color:rgb(129 140 248/var(--tw-bg-opacity))}.bg-opacity-75{--tw-bg-opacity:0.75}.bg-opacity-90{--tw-bg-opacity:0.9}.fill-transparent{fill:transparent}.fill-current{fill:currentColor}.stroke-gray-300{stroke:#d1d5db}.stroke-white{stroke:#fff}.stroke-\[3\]{stroke-width:3}.p-3{padding:.75rem}.p-2{padding:.5rem}.p-10{padding:2.5rem}.p-4{padding:1rem}.p-1{padding:.25rem}.p-0{padding:0}.p-\[6px\]{padding:6px}.px-2{padding-left:.5rem;padding-right:.5rem}.py-1{padding-bottom:.25rem;padding-top:.25rem}.py-2{padding-bottom:.5rem;padding-top:.5rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-4{padding-left:1rem;padding-right:1rem}.py-3{padding-bottom:.75rem;padding-top:.75rem}.px-3{padding-left:.75rem;padding-right:.75rem}.py-4{padding-bottom:1rem;padding-top:1rem}.px-8{padding-left:2rem;padding-right:2rem}.py-0{padding-bottom:0;padding-top:0}.\!px-3{padding-left:.75rem!important;padding-right:.75rem!important}.pr-0{padding-right:0}.pt-2{padding-top:.5rem}.pl-3{padding-left:.75rem}.pr-9{padding-right:2.25rem}.pb-4{padding-bottom:1rem}.pt-0{padding-top:0}.pl-2{padding-left:.5rem}.pr-4{padding-right:1rem}.pl-1{padding-left:.25rem}.pr-10{padding-right:2.5rem}.pr-2{padding-right:.5rem}.pr-3{padding-right:.75rem}.pb-2{padding-bottom:.5rem}.pb-\[10px\]{padding-bottom:10px}.pr-1{padding-right:.25rem}.pr-\[1\.7rem\]{padding-right:1.7rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.font-sans{font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem}.text-lg,.text-xl{line-height:1.75rem}.text-xl{font-size:1.25rem}.text-\[10px\]{font-size:10px}.font-semibold{font-weight:600}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-normal{font-weight:400}.uppercase{text-transform:uppercase}.lowercase{text-transform:lowercase}.capitalize{text-transform:capitalize}.leading-6{line-height:1.5rem}.leading-5{line-height:1.25rem}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.text-red-700{--tw-text-opacity:1;color:rgb(185 28 28/var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity:1;color:rgb(75 85 99/var(--tw-text-opacity))}.text-black{--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity))}.text-gray-100{--tw-text-opacity:1;color:rgb(243 244 246/var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.text-indigo-600{--tw-text-opacity:1;color:rgb(79 70 229/var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity))}.text-red-500{--tw-text-opacity:1;color:rgb(239 68 68/var(--tw-text-opacity))}.\!text-indigo-600{--tw-text-opacity:1!important;color:rgb(79 70 229/var(--tw-text-opacity))!important}.opacity-100{opacity:1}.opacity-0{opacity:0}.opacity-50{opacity:.5}.opacity-90{opacity:.9}.opacity-80{opacity:.8}.shadow-lg{--tw-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1);--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color)}.shadow-\[0_0_10px_2px_rgba\(0\2c 0\2c 0\2c 0\.3\)\],.shadow-lg{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-\[0_0_10px_2px_rgba\(0\2c 0\2c 0\2c 0\.3\)\]{--tw-shadow:0 0 10px 2px rgba(0,0,0,.3);--tw-shadow-colored:0 0 10px 2px var(--tw-shadow-color)}.shadow-sm{--tw-shadow:0 1px 2px 0 rgba(0,0,0,.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.shadow-md,.shadow-sm{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-md{--tw-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -2px rgba(0,0,0,.1);--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -2px var(--tw-shadow-color)}.outline-none{outline:2px solid transparent;outline-offset:2px}.ring-1{--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(1px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.ring-0,.ring-1{box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-0{--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(var(--tw-ring-offset-width)) var(--tw-ring-color)}.ring-black{--tw-ring-opacity:1;--tw-ring-color:rgb(0 0 0/var(--tw-ring-opacity))}.ring-opacity-5{--tw-ring-opacity:0.05}.blur{--tw-blur:blur(8px)}.blur,.invert{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)}.invert{--tw-invert:invert(100%)}.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-duration:.15s;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-colors{transition-duration:.15s;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1)}.duration-100{transition-duration:.1s}.duration-200{transition-duration:.2s}.duration-150{transition-duration:.15s}.ease-in{transition-timing-function:cubic-bezier(.4,0,1,1)}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.\[stroke-dasharray\:6\2c 4\]{stroke-dasharray:6,4}.\[stroke-linecap\:round\]{stroke-linecap:round}.\[stroke-linejoin\:round\]{stroke-linejoin:round}.\[writing-mode\:vertical-lr\]{writing-mode:vertical-lr}.checked\:border-indigo-600:checked{--tw-border-opacity:1;border-color:rgb(79 70 229/var(--tw-border-opacity))}.checked\:bg-indigo-600:checked{--tw-bg-opacity:1;background-color:rgb(79 70 229/var(--tw-bg-opacity))}.hover\:whitespace-normal:hover{white-space:normal}.hover\:bg-gray-200:hover{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity))}.hover\:bg-indigo-600:hover{--tw-bg-opacity:1;background-color:rgb(79 70 229/var(--tw-bg-opacity))}.hover\:bg-indigo-500:hover{--tw-bg-opacity:1;background-color:rgb(99 102 241/var(--tw-bg-opacity))}.hover\:text-white:hover{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.focus\:relative:focus{position:relative}.focus\:z-50:focus{z-index:50}.focus\:border-indigo-500:focus{--tw-border-opacity:1;border-color:rgb(99 102 241/var(--tw-border-opacity))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:outline-1:focus{outline-width:1px}.focus\:ring-1: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(1px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.focus\:ring-1:focus,.focus\:ring-2:focus{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)}.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(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-indigo-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(99 102 241/var(--tw-ring-opacity))}.focus\:ring-indigo-600:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(79 70 229/var(--tw-ring-opacity))}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px}.focus\:ring-offset-0:focus{--tw-ring-offset-width:0px}.focus-visible\:ring-2:focus-visible{--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-visible\:ring-white\/75:focus-visible{--tw-ring-color:hsla(0,0%,100%,.75)}.focus-visible\:ring-white:focus-visible{--tw-ring-opacity:1;--tw-ring-color:rgb(255 255 255/var(--tw-ring-opacity))}.focus-visible\:ring-indigo-500:focus-visible{--tw-ring-opacity:1;--tw-ring-color:rgb(99 102 241/var(--tw-ring-opacity))}.focus-visible\:ring-opacity-75:focus-visible{--tw-ring-opacity:0.75}.group:hover .group-hover\:flex{display:flex}[class~=theme-dark] .dark\:border-gray-500{--tw-border-opacity:1;border-color:rgb(107 114 128/var(--tw-border-opacity))}[class~=theme-dark] .dark\:border-gray-600{--tw-border-opacity:1;border-color:rgb(75 85 99/var(--tw-border-opacity))}[class~=theme-dark] .dark\:border-gray-700{--tw-border-opacity:1;border-color:rgb(55 65 81/var(--tw-border-opacity))}[class~=theme-dark] .dark\:border-gray-100{--tw-border-opacity:1;border-color:rgb(243 244 246/var(--tw-border-opacity))}[class~=theme-dark] .dark\:border-r-gray-700{--tw-border-opacity:1;border-right-color:rgb(55 65 81/var(--tw-border-opacity))}[class~=theme-dark] .dark\:bg-gray-800{--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-gray-900{--tw-bg-opacity:1;background-color:rgb(17 24 39/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-gray-700{--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-black{--tw-bg-opacity:1;background-color:rgb(0 0 0/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-black\/50{background-color:rgba(0,0,0,.5)}[class~=theme-dark] .dark\:bg-indigo-700{--tw-bg-opacity:1;background-color:rgb(67 56 202/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-yellow-700{--tw-bg-opacity:1;background-color:rgb(161 98 7/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-gray-100\/90{background-color:rgba(243,244,246,.9)}[class~=theme-dark] .dark\:bg-gray-600{--tw-bg-opacity:1;background-color:rgb(75 85 99/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-gray-200\/75{background-color:rgba(229,231,235,.75)}[class~=theme-dark] .dark\:bg-gray-500{--tw-bg-opacity:1;background-color:rgb(107 114 128/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-opacity-80{--tw-bg-opacity:0.8}[class~=theme-dark] .dark\:stroke-gray-500{stroke:#6b7280}[class~=theme-dark] .dark\:stroke-gray-700{stroke:#374151}[class~=theme-dark] .dark\:text-gray-300{--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity))}[class~=theme-dark] .dark\:text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}[class~=theme-dark] .dark\:text-gray-200{--tw-text-opacity:1;color:rgb(229 231 235/var(--tw-text-opacity))}[class~=theme-dark] .dark\:text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}[class~=theme-dark] .dark\:text-gray-50{--tw-text-opacity:1;color:rgb(249 250 251/var(--tw-text-opacity))}[class~=theme-dark] .dark\:text-indigo-500{--tw-text-opacity:1;color:rgb(99 102 241/var(--tw-text-opacity))}[class~=theme-dark] .dark\:\!text-indigo-400{--tw-text-opacity:1!important;color:rgb(129 140 248/var(--tw-text-opacity))!important}[class~=theme-dark] .dark\:ring-white{--tw-ring-opacity:1;--tw-ring-color:rgb(255 255 255/var(--tw-ring-opacity))}[class~=theme-dark] .dark\:ring-opacity-20{--tw-ring-opacity:0.2}[class~=theme-dark] .hover\:dark\:text-gray-100:hover{--tw-text-opacity:1;color:rgb(243 244 246/var(--tw-text-opacity))}@media (min-width:640px){.sm\:inline{display:inline}.sm\:text-sm{font-size:.875rem;line-height:1.25rem}}@media (min-width:768px){.md\:block{display:block}.md\:flex-row{flex-direction:row}}
1
+ /*! tailwindcss v3.2.4 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}html{-webkit-text-size-adjust:100%;font-feature-settings:normal;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}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,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;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{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-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,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]{display:none}*,:after,:before{--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-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:rgba(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: }::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-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:rgba(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: }.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.sr-only{clip:rect(0,0,0,0);border-width:0;height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.-inset-2{bottom:-.5rem;left:-.5rem;right:-.5rem;top:-.5rem}.inset-y-0{bottom:0;top:0}.right-0{right:0}.top-0{top:0}.left-0{left:0}.bottom-3{bottom:.75rem}.top-\[-5px\]{top:-5px}.top-\[-2px\]{top:-2px}.bottom-0{bottom:0}.left-\[-7px\]{left:-7px}.right-\[-7px\]{right:-7px}.right-8{right:2rem}.top-\[-46px\]{top:-46px}.right-full{right:100%}.left-full{left:100%}.top-\[-60px\]{top:-60px}.left-1\/2{left:50%}.top-full{top:100%}.z-10{z-index:10}.z-50{z-index:50}.z-30{z-index:30}.z-\[5\]{z-index:5}.z-20{z-index:20}.m-auto{margin:auto}.m-2{margin:.5rem}.mx-2{margin-left:.5rem;margin-right:.5rem}.my-2{margin-bottom:.5rem;margin-top:.5rem}.my-0{margin-bottom:0;margin-top:0}.my-20{margin-bottom:5rem;margin-top:5rem}.mx-auto{margin-left:auto;margin-right:auto}.my-4{margin-bottom:1rem;margin-top:1rem}.mr-3{margin-right:.75rem}.mt-1{margin-top:.25rem}.ml-\[70px\]{margin-left:70px}.mb-2{margin-bottom:.5rem}.ml-auto{margin-left:auto}.mt-auto{margin-top:auto}.mb-0\.5{margin-bottom:.125rem}.mt-1\.5{margin-top:.375rem}.mb-0{margin-bottom:0}.mr-2{margin-right:.5rem}.ml-3{margin-left:.75rem}.mt-2{margin-top:.5rem}.mb-px{margin-bottom:1px}.ml-2{margin-left:.5rem}.mr-6{margin-right:1.5rem}.mb-1{margin-bottom:.25rem}.mr-1{margin-right:.25rem}.mt-4{margin-top:1rem}.mb-4{margin-bottom:1rem}.mt-3{margin-top:.75rem}.mt-8{margin-top:2rem}.mt-0{margin-top:0}.ml-1{margin-left:.25rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.\!flex{display:flex!important}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-10{height:2.5rem}.h-fit{height:-moz-fit-content;height:fit-content}.h-\[38px\]{height:38px}.h-auto{height:auto}.h-full{height:100%}.h-1{height:.25rem}.h-\[20px\]{height:20px}.h-\[16px\]{height:16px}.h-4{height:1rem}.h-\[30px\]{height:30px}.h-\[45px\]{height:45px}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-\[700px\]{height:700px}.h-\[80vh\]{height:80vh}.h-\[1px\]{height:1px}.h-\[14px\]{height:14px}.h-\[24px\]{height:24px}.h-0{height:0}.max-h-\[400px\]{max-height:400px}.max-h-\[50vh\]{max-height:50vh}.max-h-\[300px\]{max-height:300px}.min-h-52{min-height:13rem}.min-h-5{min-height:1.25rem}.min-h-48{min-height:12rem}.min-h-\[50px\]{min-height:50px}.min-h-\[700px\]{min-height:700px}.min-h-96{min-height:24rem}.w-full{width:100%}.w-\[500px\]{width:500px}.w-1\/4{width:25%}.w-3\/4{width:75%}.w-max{width:-moz-max-content;width:max-content}.w-40{width:10rem}.w-\[44px\]{width:44px}.w-\[16px\]{width:16px}.w-auto{width:auto}.w-\[270px\]{width:270px}.w-\[300px\]{width:300px}.w-5{width:1.25rem}.w-3{width:.75rem}.w-7{width:1.75rem}.w-9{width:2.25rem}.w-11{width:2.75rem}.w-\[52px\]{width:52px}.w-\[68px\]{width:68px}.w-\[76px\]{width:76px}.w-\[84px\]{width:84px}.w-\[92px\]{width:92px}.w-\[100px\]{width:100px}.w-\[108px\]{width:108px}.w-\[116px\]{width:116px}.w-4{width:1rem}.w-56{width:14rem}.w-\[350px\]{width:350px}.w-\[260px\]{width:260px}.w-44{width:11rem}.w-\[460px\]{width:460px}.w-\[19\.25\%\]{width:19.25%}.w-11\/12{width:91.666667%}.w-1\/12{width:8.333333%}.w-8{width:2rem}.w-\[50\%\]{width:50%}.w-32{width:8rem}.w-36{width:9rem}.w-16{width:4rem}.w-fit{width:-moz-fit-content;width:fit-content}.w-\[420px\]{width:420px}.w-48{width:12rem}.w-\[14px\]{width:14px}.w-64{width:16rem}.w-80{width:20rem}.w-\[20px\]{width:20px}.w-\[246px\]{width:246px}.w-52{width:13rem}.w-0{width:0}.min-w-\[300px\]{min-width:300px}.min-w-\[400px\]{min-width:400px}.min-w-\[350px\]{min-width:350px}.min-w-\[260px\]{min-width:260px}.max-w-\[500px\]{max-width:500px}.max-w-md{max-width:28rem}.max-w-80{max-width:20rem}.max-w-full{max-width:100%}.max-w-48{max-width:12rem}.max-w-\[300px\]{max-width:300px}.max-w-\[400px\]{max-width:400px}.max-w-lg{max-width:32rem}.flex-1{flex:1 1 0%}.flex-shrink-0,.shrink-0{flex-shrink:0}.shrink{flex-shrink:1}.flex-grow-0{flex-grow:0}.flex-grow{flex-grow:1}.table-auto{table-layout:auto}.table-fixed{table-layout:fixed}.origin-top-right{transform-origin:top right}.origin-top-left{transform-origin:top left}.translate-x-6{--tw-translate-x:1.5rem}.translate-x-0,.translate-x-6{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-0{--tw-translate-x:0px}.translate-y-1{--tw-translate-y:0.25rem}.translate-y-0,.translate-y-1{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-0{--tw-translate-y:0px}.translate-x-5{--tw-translate-x:1.25rem}.-translate-x-1\/2,.translate-x-5{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-x-1\/2{--tw-translate-x:-50%}.rotate-90{--tw-rotate:90deg}.-rotate-180,.rotate-90{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-rotate-180{--tw-rotate:-180deg}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-default{cursor:default}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.cursor-ew-resize{cursor:ew-resize}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize{resize:both}.list-inside{list-style-position:inside}.list-disc{list-style-type:disc}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-row{flex-direction:row}.flex-col{flex-direction:column}.flex-col-reverse{flex-direction:column-reverse}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-4{gap:1rem}.gap-1{gap:.25rem}.gap-\[2px\]{gap:2px}.gap-2{gap:.5rem}.gap-6{gap:1.5rem}.gap-3{gap:.75rem}.gap-0{gap:0}.gap-x-2{-moz-column-gap:.5rem;column-gap:.5rem}.space-y-5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(1.25rem*var(--tw-space-y-reverse));margin-top:calc(1.25rem*(1 - var(--tw-space-y-reverse)))}.self-end{align-self:flex-end}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-clip{overflow:clip}.overflow-scroll{overflow:scroll}.overflow-x-hidden{overflow-x:hidden}.text-ellipsis{text-overflow:ellipsis}.whitespace-normal{white-space:normal}.whitespace-nowrap{white-space:nowrap}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-sm{border-radius:.125rem}.rounded-full{border-radius:9999px}.rounded-none{border-radius:0}.rounded-\[4px\]{border-radius:4px}.rounded-l-md{border-bottom-left-radius:.375rem;border-top-left-radius:.375rem}.rounded-l-none{border-bottom-left-radius:0;border-top-left-radius:0}.rounded-r-none{border-bottom-right-radius:0;border-top-right-radius:0}.rounded-r-md{border-bottom-right-radius:.375rem;border-top-right-radius:.375rem}.rounded-t-lg{border-top-left-radius:.5rem;border-top-right-radius:.5rem}.rounded-b{border-bottom-right-radius:.25rem}.rounded-b,.rounded-l{border-bottom-left-radius:.25rem}.rounded-l{border-top-left-radius:.25rem}.rounded-r{border-bottom-right-radius:.25rem;border-top-right-radius:.25rem}.rounded-tr-none{border-top-right-radius:0}.rounded-br-none{border-bottom-right-radius:0}.rounded-tl-none{border-top-left-radius:0}.rounded-bl-none{border-bottom-left-radius:0}.border{border-width:1px}.border-2{border-width:2px}.border-x-2{border-left-width:2px;border-right-width:2px}.border-b{border-bottom-width:1px}.border-t{border-top-width:1px}.border-r{border-right-width:1px}.border-l{border-left-width:1px}.border-l-0{border-left-width:0}.border-r-0{border-right-width:0}.border-l-4{border-left-width:4px}.border-r-4{border-right-width:4px}.border-t-4{border-top-width:4px}.border-none{border-style:none}.border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity))}.border-red-400{--tw-border-opacity:1;border-color:rgb(248 113 113/var(--tw-border-opacity))}.border-transparent{border-color:transparent}.border-indigo-500{--tw-border-opacity:1;border-color:rgb(99 102 241/var(--tw-border-opacity))}.border-gray-200{--tw-border-opacity:1;border-color:rgb(229 231 235/var(--tw-border-opacity))}.border-gray-400{--tw-border-opacity:1;border-color:rgb(156 163 175/var(--tw-border-opacity))}.border-gray-900{--tw-border-opacity:1;border-color:rgb(17 24 39/var(--tw-border-opacity))}.border-gray-100{--tw-border-opacity:1;border-color:rgb(243 244 246/var(--tw-border-opacity))}.border-r-gray-200{--tw-border-opacity:1;border-right-color:rgb(229 231 235/var(--tw-border-opacity))}.border-l-amber-900{--tw-border-opacity:1;border-left-color:rgb(120 53 15/var(--tw-border-opacity))}.border-l-transparent{border-left-color:transparent}.border-r-transparent{border-right-color:transparent}.bg-gray-200{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.bg-indigo-600{--tw-bg-opacity:1;background-color:rgb(79 70 229/var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-white\/50{background-color:hsla(0,0%,100%,.5)}.bg-red-100{--tw-bg-opacity:1;background-color:rgb(254 226 226/var(--tw-bg-opacity))}.bg-black{--tw-bg-opacity:1;background-color:rgb(0 0 0/var(--tw-bg-opacity))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity))}.bg-gray-400{--tw-bg-opacity:1;background-color:rgb(156 163 175/var(--tw-bg-opacity))}.bg-indigo-100{--tw-bg-opacity:1;background-color:rgb(224 231 255/var(--tw-bg-opacity))}.bg-yellow-200{--tw-bg-opacity:1;background-color:rgb(254 240 138/var(--tw-bg-opacity))}.bg-yellow-700{--tw-bg-opacity:1;background-color:rgb(161 98 7/var(--tw-bg-opacity))}.bg-gray-500{--tw-bg-opacity:1;background-color:rgb(107 114 128/var(--tw-bg-opacity))}.bg-gray-500\/50{background-color:hsla(220,9%,46%,.5)}.bg-gray-700\/75{background-color:rgba(55,65,81,.75)}.bg-gray-800{--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}.bg-gray-600{--tw-bg-opacity:1;background-color:rgb(75 85 99/var(--tw-bg-opacity))}.bg-indigo-50{--tw-bg-opacity:1;background-color:rgb(238 242 255/var(--tw-bg-opacity))}.bg-gray-700{--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}.bg-inherit{background-color:inherit}.bg-indigo-400{--tw-bg-opacity:1;background-color:rgb(129 140 248/var(--tw-bg-opacity))}.bg-opacity-75{--tw-bg-opacity:0.75}.bg-opacity-90{--tw-bg-opacity:0.9}.fill-transparent{fill:transparent}.fill-current{fill:currentColor}.stroke-gray-300{stroke:#d1d5db}.stroke-white{stroke:#fff}.stroke-\[3\]{stroke-width:3}.p-3{padding:.75rem}.p-2{padding:.5rem}.p-10{padding:2.5rem}.p-4{padding:1rem}.p-1{padding:.25rem}.p-0{padding:0}.p-\[6px\]{padding:6px}.px-2{padding-left:.5rem;padding-right:.5rem}.py-1{padding-bottom:.25rem;padding-top:.25rem}.py-2{padding-bottom:.5rem;padding-top:.5rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-4{padding-left:1rem;padding-right:1rem}.py-3{padding-bottom:.75rem;padding-top:.75rem}.px-3{padding-left:.75rem;padding-right:.75rem}.py-4{padding-bottom:1rem;padding-top:1rem}.px-8{padding-left:2rem;padding-right:2rem}.py-0{padding-bottom:0;padding-top:0}.\!px-3{padding-left:.75rem!important;padding-right:.75rem!important}.pr-0{padding-right:0}.pt-2{padding-top:.5rem}.pl-3{padding-left:.75rem}.pr-9{padding-right:2.25rem}.pb-4{padding-bottom:1rem}.pt-0{padding-top:0}.pl-2{padding-left:.5rem}.pr-4{padding-right:1rem}.pl-1{padding-left:.25rem}.pr-10{padding-right:2.5rem}.pr-2{padding-right:.5rem}.pr-3{padding-right:.75rem}.pb-2{padding-bottom:.5rem}.pb-\[10px\]{padding-bottom:10px}.pr-1{padding-right:.25rem}.pr-\[1\.7rem\]{padding-right:1.7rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.font-sans{font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem}.text-lg,.text-xl{line-height:1.75rem}.text-xl{font-size:1.25rem}.text-\[10px\]{font-size:10px}.font-semibold{font-weight:600}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-normal{font-weight:400}.uppercase{text-transform:uppercase}.lowercase{text-transform:lowercase}.capitalize{text-transform:capitalize}.leading-6{line-height:1.5rem}.leading-5{line-height:1.25rem}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.text-red-700{--tw-text-opacity:1;color:rgb(185 28 28/var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity:1;color:rgb(75 85 99/var(--tw-text-opacity))}.text-black{--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity))}.text-gray-100{--tw-text-opacity:1;color:rgb(243 244 246/var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.text-indigo-600{--tw-text-opacity:1;color:rgb(79 70 229/var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity))}.text-red-500{--tw-text-opacity:1;color:rgb(239 68 68/var(--tw-text-opacity))}.\!text-indigo-600{--tw-text-opacity:1!important;color:rgb(79 70 229/var(--tw-text-opacity))!important}.opacity-100{opacity:1}.opacity-0{opacity:0}.opacity-50{opacity:.5}.opacity-90{opacity:.9}.opacity-80{opacity:.8}.shadow-lg{--tw-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1);--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color)}.shadow-\[0_0_10px_2px_rgba\(0\2c 0\2c 0\2c 0\.3\)\],.shadow-lg{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-\[0_0_10px_2px_rgba\(0\2c 0\2c 0\2c 0\.3\)\]{--tw-shadow:0 0 10px 2px rgba(0,0,0,.3);--tw-shadow-colored:0 0 10px 2px var(--tw-shadow-color)}.shadow-sm{--tw-shadow:0 1px 2px 0 rgba(0,0,0,.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.shadow-md,.shadow-sm{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-md{--tw-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -2px rgba(0,0,0,.1);--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -2px var(--tw-shadow-color)}.outline-none{outline:2px solid transparent;outline-offset:2px}.ring-1{--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(1px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.ring-0,.ring-1{box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-0{--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(var(--tw-ring-offset-width)) var(--tw-ring-color)}.ring-black{--tw-ring-opacity:1;--tw-ring-color:rgb(0 0 0/var(--tw-ring-opacity))}.ring-opacity-5{--tw-ring-opacity:0.05}.blur{--tw-blur:blur(8px)}.blur,.invert{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)}.invert{--tw-invert:invert(100%)}.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-duration:.15s;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-colors{transition-duration:.15s;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1)}.duration-100{transition-duration:.1s}.duration-200{transition-duration:.2s}.duration-150{transition-duration:.15s}.ease-in{transition-timing-function:cubic-bezier(.4,0,1,1)}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.\[stroke-dasharray\:6\2c 4\]{stroke-dasharray:6,4}.\[stroke-linecap\:round\]{stroke-linecap:round}.\[stroke-linejoin\:round\]{stroke-linejoin:round}.\[writing-mode\:vertical-lr\]{writing-mode:vertical-lr}.checked\:border-indigo-600:checked{--tw-border-opacity:1;border-color:rgb(79 70 229/var(--tw-border-opacity))}.checked\:bg-indigo-600:checked{--tw-bg-opacity:1;background-color:rgb(79 70 229/var(--tw-bg-opacity))}.hover\:whitespace-normal:hover{white-space:normal}.hover\:bg-gray-200:hover{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity))}.hover\:bg-indigo-600:hover{--tw-bg-opacity:1;background-color:rgb(79 70 229/var(--tw-bg-opacity))}.hover\:bg-indigo-500:hover{--tw-bg-opacity:1;background-color:rgb(99 102 241/var(--tw-bg-opacity))}.hover\:text-white:hover{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.focus\:relative:focus{position:relative}.focus\:z-50:focus{z-index:50}.focus\:border-indigo-500:focus{--tw-border-opacity:1;border-color:rgb(99 102 241/var(--tw-border-opacity))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:outline-1:focus{outline-width:1px}.focus\:ring-1: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(1px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.focus\:ring-1:focus,.focus\:ring-2:focus{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)}.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(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-indigo-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(99 102 241/var(--tw-ring-opacity))}.focus\:ring-indigo-600:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(79 70 229/var(--tw-ring-opacity))}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px}.focus\:ring-offset-0:focus{--tw-ring-offset-width:0px}.focus-visible\:ring-2:focus-visible{--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-visible\:ring-white\/75:focus-visible{--tw-ring-color:hsla(0,0%,100%,.75)}.focus-visible\:ring-white:focus-visible{--tw-ring-opacity:1;--tw-ring-color:rgb(255 255 255/var(--tw-ring-opacity))}.focus-visible\:ring-indigo-500:focus-visible{--tw-ring-opacity:1;--tw-ring-color:rgb(99 102 241/var(--tw-ring-opacity))}.focus-visible\:ring-opacity-75:focus-visible{--tw-ring-opacity:0.75}.group:hover .group-hover\:flex{display:flex}[class~=theme-dark] .dark\:border-gray-500{--tw-border-opacity:1;border-color:rgb(107 114 128/var(--tw-border-opacity))}[class~=theme-dark] .dark\:border-gray-600{--tw-border-opacity:1;border-color:rgb(75 85 99/var(--tw-border-opacity))}[class~=theme-dark] .dark\:border-gray-700{--tw-border-opacity:1;border-color:rgb(55 65 81/var(--tw-border-opacity))}[class~=theme-dark] .dark\:border-gray-100{--tw-border-opacity:1;border-color:rgb(243 244 246/var(--tw-border-opacity))}[class~=theme-dark] .dark\:border-r-gray-700{--tw-border-opacity:1;border-right-color:rgb(55 65 81/var(--tw-border-opacity))}[class~=theme-dark] .dark\:bg-gray-800{--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-gray-900{--tw-bg-opacity:1;background-color:rgb(17 24 39/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-gray-700{--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-black{--tw-bg-opacity:1;background-color:rgb(0 0 0/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-black\/50{background-color:rgba(0,0,0,.5)}[class~=theme-dark] .dark\:bg-indigo-700{--tw-bg-opacity:1;background-color:rgb(67 56 202/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-yellow-700{--tw-bg-opacity:1;background-color:rgb(161 98 7/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-gray-100\/90{background-color:rgba(243,244,246,.9)}[class~=theme-dark] .dark\:bg-gray-600{--tw-bg-opacity:1;background-color:rgb(75 85 99/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-gray-200\/75{background-color:rgba(229,231,235,.75)}[class~=theme-dark] .dark\:bg-gray-500{--tw-bg-opacity:1;background-color:rgb(107 114 128/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-opacity-80{--tw-bg-opacity:0.8}[class~=theme-dark] .dark\:stroke-gray-500{stroke:#6b7280}[class~=theme-dark] .dark\:stroke-gray-700{stroke:#374151}[class~=theme-dark] .dark\:text-gray-300{--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity))}[class~=theme-dark] .dark\:text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}[class~=theme-dark] .dark\:text-gray-200{--tw-text-opacity:1;color:rgb(229 231 235/var(--tw-text-opacity))}[class~=theme-dark] .dark\:text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}[class~=theme-dark] .dark\:text-gray-50{--tw-text-opacity:1;color:rgb(249 250 251/var(--tw-text-opacity))}[class~=theme-dark] .dark\:text-indigo-500{--tw-text-opacity:1;color:rgb(99 102 241/var(--tw-text-opacity))}[class~=theme-dark] .dark\:\!text-indigo-400{--tw-text-opacity:1!important;color:rgb(129 140 248/var(--tw-text-opacity))!important}[class~=theme-dark] .dark\:ring-white{--tw-ring-opacity:1;--tw-ring-color:rgb(255 255 255/var(--tw-ring-opacity))}[class~=theme-dark] .dark\:ring-opacity-20{--tw-ring-opacity:0.2}[class~=theme-dark] .hover\:dark\:text-gray-100:hover{--tw-text-opacity:1;color:rgb(243 244 246/var(--tw-text-opacity))}@media (min-width:640px){.sm\:inline{display:inline}.sm\:text-sm{font-size:.875rem;line-height:1.25rem}}@media (min-width:768px){.md\:block{display:block}.md\:flex-row{flex-direction:row}}
package/package.json CHANGED
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "name": "@parca/profile",
3
- "version": "0.19.23",
3
+ "version": "0.19.24",
4
4
  "description": "Profile viewing libraries",
5
5
  "dependencies": {
6
6
  "@floating-ui/react": "^0.27.12",
7
7
  "@headlessui/react": "^1.7.19",
8
8
  "@iconify/react": "^4.0.0",
9
9
  "@parca/client": "0.17.3",
10
- "@parca/components": "0.16.350",
10
+ "@parca/components": "0.16.351",
11
11
  "@parca/dynamicsize": "0.16.65",
12
- "@parca/hooks": "0.0.95",
12
+ "@parca/hooks": "0.0.96",
13
13
  "@parca/icons": "0.16.72",
14
14
  "@parca/parser": "0.16.79",
15
- "@parca/store": "0.16.179",
15
+ "@parca/store": "0.16.180",
16
16
  "@parca/utilities": "0.0.104",
17
17
  "@popperjs/core": "^2.11.8",
18
18
  "@protobuf-ts/runtime-rpc": "^2.5.0",
@@ -78,5 +78,5 @@
78
78
  "access": "public",
79
79
  "registry": "https://registry.npmjs.org/"
80
80
  },
81
- "gitHead": "f14c580082c5cabf83aa535b207d4bb2b05b31a6"
81
+ "gitHead": "a14a290bb5329cb3faaf18c171f1278df2821bda"
82
82
  }
@@ -0,0 +1,76 @@
1
+ // Copyright 2022 The Parca Authors
2
+ // Licensed under the Apache License, Version 2.0 (the "License");
3
+ // you may not use this file except in compliance with the License.
4
+ // You may obtain a copy of the License at
5
+ //
6
+ // http://www.apache.org/licenses/LICENSE-2.0
7
+ //
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+
14
+ import type {ProfileFilter} from '@parca/store';
15
+
16
+ export interface FilterPreset {
17
+ key: string;
18
+ name: string;
19
+ description: string;
20
+ filters: Array<Omit<ProfileFilter, 'id'>>;
21
+ }
22
+
23
+ export const filterPresets: FilterPreset[] = [
24
+ {
25
+ key: 'go_runtime_expected_off_cpu',
26
+ name: 'Go Runtime Expected Off-CPU',
27
+ description: 'Excludes expected Go runtime blocking functions',
28
+ filters: [
29
+ {
30
+ type: 'stack',
31
+ field: 'function_name',
32
+ matchType: 'not_equal',
33
+ value: 'runtime.usleep',
34
+ },
35
+ {
36
+ type: 'stack',
37
+ field: 'function_name',
38
+ matchType: 'not_equal',
39
+ value: 'runtime.futex',
40
+ },
41
+ ],
42
+ },
43
+ {
44
+ key: 'rust_runtime_expected_off_cpu',
45
+ name: 'Rust Expected Off-CPU',
46
+ description: 'Excludes expected Rust runtime blocking functions',
47
+ filters: [
48
+ {
49
+ type: 'stack',
50
+ field: 'function_name',
51
+ matchType: 'not_equal',
52
+ value: 'parking_lot_core::thread_parker::imp::ThreadParker::futex_wait',
53
+ },
54
+ {
55
+ type: 'stack',
56
+ field: 'function_name',
57
+ matchType: 'not_equal',
58
+ value: 'tokio::runtime::time::Driver::park_internal',
59
+ },
60
+ {
61
+ type: 'stack',
62
+ field: 'function_name',
63
+ matchType: 'not_equal',
64
+ value: 'futex_wait',
65
+ },
66
+ ],
67
+ },
68
+ ];
69
+
70
+ export const isPresetKey = (key: string): boolean => {
71
+ return filterPresets.some(preset => preset.key === key);
72
+ };
73
+
74
+ export const getPresetByKey = (key: string): FilterPreset | undefined => {
75
+ return filterPresets.find(preset => preset.key === key);
76
+ };
@@ -18,9 +18,15 @@ import cx from 'classnames';
18
18
 
19
19
  import {Button, Input, Select, type SelectItem} from '@parca/components';
20
20
 
21
+ import {filterPresets, getPresetByKey, isPresetKey} from './filterPresets';
21
22
  import {useProfileFilters, type ProfileFilter} from './useProfileFilters';
22
23
 
23
24
  export const isFilterComplete = (filter: ProfileFilter): boolean => {
25
+ // For preset filters, only need type and value
26
+ if (filter.type != null && isPresetKey(filter.type)) {
27
+ return filter.value !== '' && filter.type != null;
28
+ }
29
+ // For regular filters, need all fields
24
30
  return (
25
31
  filter.value !== '' && filter.type != null && filter.field != null && filter.matchType != null
26
32
  );
@@ -53,6 +59,19 @@ const filterTypeItems: SelectItem[] = [
53
59
  ),
54
60
  },
55
61
  },
62
+ ...filterPresets.map(preset => ({
63
+ key: preset.key,
64
+ element: {
65
+ active: <>{preset.name}</>,
66
+ expanded: (
67
+ <>
68
+ <span>{preset.name}</span>
69
+ <br />
70
+ <span className="text-xs">{preset.description}</span>
71
+ </>
72
+ ),
73
+ },
74
+ })),
56
75
  ];
57
76
 
58
77
  const fieldItems: SelectItem[] = [
@@ -181,6 +200,7 @@ const ProfileFilters = (): JSX.Element => {
181
200
  {filtersToRender.map(filter => {
182
201
  const isNumberField = filter.field === 'address' || filter.field === 'line_number';
183
202
  const matchTypeItems = isNumberField ? numberMatchTypeItems : stringMatchTypeItems;
203
+ const isPresetFilter = filter.type != null && isPresetKey(filter.type);
184
204
 
185
205
  return (
186
206
  <div key={filter.id} className="flex items-center gap-0">
@@ -189,20 +209,45 @@ const ProfileFilters = (): JSX.Element => {
189
209
  selectedKey={filter.type}
190
210
  placeholder="Select Filter"
191
211
  onSelection={key => {
192
- const newType = key as 'stack' | 'frame';
193
- updateFilter(filter.id, {
194
- type: newType,
195
- field: filter.field ?? 'function_name',
196
- matchType: filter.matchType ?? 'contains',
197
- });
212
+ // Check if this is a preset selection
213
+ if (isPresetKey(key)) {
214
+ const preset = getPresetByKey(key);
215
+ if (preset != null) {
216
+ updateFilter(filter.id, {
217
+ type: preset.key,
218
+ field: undefined,
219
+ matchType: undefined,
220
+ value: preset.name,
221
+ });
222
+ }
223
+ } else {
224
+ const newType = key as 'stack' | 'frame';
225
+
226
+ // Check if we're converting a preset filter to a regular filter
227
+ if (filter.type != null && isPresetKey(filter.type)) {
228
+ updateFilter(filter.id, {
229
+ type: newType,
230
+ field: 'function_name',
231
+ matchType: 'contains',
232
+ value: '',
233
+ });
234
+ } else {
235
+ updateFilter(filter.id, {
236
+ type: newType,
237
+ field: filter.field ?? 'function_name',
238
+ matchType: filter.matchType ?? 'contains',
239
+ });
240
+ }
241
+ }
198
242
  }}
199
243
  className={cx(
200
- 'rounded-l-md pr-1 gap-0 focus:z-50 focus:relative focus:outline-1 rounded-r-none ',
201
- filter.type != null ? 'border-r-0 w-28' : 'w-32'
244
+ 'rounded-l-md pr-1 gap-0 focus:z-50 focus:relative focus:outline-1',
245
+ isPresetFilter ? 'rounded-r-none border-r-0' : 'rounded-r-none',
246
+ filter.type != null ? 'border-r-0 w-auto' : 'w-32'
202
247
  )}
203
248
  />
204
249
 
205
- {filter.type != null && (
250
+ {filter.type != null && !isPresetFilter && (
206
251
  <>
207
252
  <Select
208
253
  items={fieldItems}
@@ -247,9 +292,16 @@ const ProfileFilters = (): JSX.Element => {
247
292
  <Button
248
293
  variant="neutral"
249
294
  onClick={() => {
250
- if (localFilters.length === 1) {
295
+ // If we're displaying local filters and this is the last one, reset everything
296
+ if (localFilters.length > 0 && localFilters.length === 1) {
251
297
  resetFilters();
252
- } else {
298
+ }
299
+ // If we're displaying applied filters and this is the last one, reset everything
300
+ else if (localFilters.length === 0 && filtersToRender.length === 1) {
301
+ resetFilters();
302
+ }
303
+ // Otherwise, just remove this specific filter
304
+ else {
253
305
  removeFilter(filter.id);
254
306
  }
255
307
  }}
@@ -278,10 +330,11 @@ const ProfileFilters = (): JSX.Element => {
278
330
  )}
279
331
  </div>
280
332
 
281
- {localFilters.length > 0 && hasUnsavedChanges && localFilters.some(isFilterComplete) && (
333
+ {localFilters.length > 0 && (
282
334
  <Button
283
335
  variant="primary"
284
336
  onClick={onApplyFilters}
337
+ disabled={!hasUnsavedChanges || !localFilters.some(isFilterComplete)}
285
338
  className={cx('flex items-center gap-2 self-end')}
286
339
  >
287
340
  <span>Apply</span>
@@ -22,13 +22,35 @@ import {
22
22
  type ProfileFilter,
23
23
  } from '@parca/store';
24
24
 
25
+ import {getPresetByKey, isPresetKey, type FilterPreset} from './filterPresets';
25
26
  import {useProfileFiltersUrlState} from './useProfileFiltersUrlState';
26
27
 
27
28
  export type {ProfileFilter};
28
29
 
29
30
  // Convert ProfileFilter[] to protobuf Filter[] matching the expected structure
30
31
  export const convertToProtoFilters = (profileFilters: ProfileFilter[]): Filter[] => {
31
- return profileFilters
32
+ // First, expand any preset filters to their constituent filters
33
+ const expandedFilters: ProfileFilter[] = [];
34
+
35
+ for (const filter of profileFilters) {
36
+ if (filter.type != null && isPresetKey(filter.type)) {
37
+ // This is a preset filter, expand it
38
+ const preset = getPresetByKey(filter.type);
39
+ if (preset != null) {
40
+ preset.filters.forEach((presetFilter, index) => {
41
+ expandedFilters.push({
42
+ ...presetFilter,
43
+ id: `${filter.id}-expanded-${index}`,
44
+ });
45
+ });
46
+ }
47
+ } else {
48
+ // Regular filter, add as is
49
+ expandedFilters.push(filter);
50
+ }
51
+ }
52
+
53
+ return expandedFilters
32
54
  .filter(f => f.value !== '' && f.type != null && f.field != null && f.matchType != null) // Only include complete filters with values
33
55
  .map(f => {
34
56
  // Build the condition based on field type
@@ -120,6 +142,7 @@ export const useProfileFilters = (): {
120
142
  removeFilter: (id: string) => void;
121
143
  updateFilter: (id: string, updates: Partial<ProfileFilter>) => void;
122
144
  resetFilters: () => void;
145
+ applyPreset: (preset: FilterPreset) => void;
123
146
  } => {
124
147
  const {appliedFilters, setAppliedFilters} = useProfileFiltersUrlState();
125
148
  const dispatch = useAppDispatch();
@@ -151,8 +174,23 @@ export const useProfileFilters = (): {
151
174
  }, []);
152
175
 
153
176
  const hasUnsavedChanges = useMemo(() => {
154
- const localWithValues = localFilters.filter(f => f.value !== '');
155
- const appliedWithValues = (appliedFilters ?? []).filter(f => f.value !== '');
177
+ const localWithValues = localFilters.filter(f => {
178
+ // For preset filters, only need type and value
179
+ if (f.type != null && isPresetKey(f.type)) {
180
+ return f.value !== '' && f.type != null;
181
+ }
182
+ // For regular filters, need all fields
183
+ return f.value !== '' && f.type != null && f.field != null && f.matchType != null;
184
+ });
185
+
186
+ const appliedWithValues = (appliedFilters ?? []).filter(f => {
187
+ // For preset filters, only need type and value
188
+ if (f.type != null && isPresetKey(f.type)) {
189
+ return f.value !== '' && f.type != null;
190
+ }
191
+ // For regular filters, need all fields
192
+ return f.value !== '' && f.type != null && f.field != null && f.matchType != null;
193
+ });
156
194
 
157
195
  if (localWithValues.length !== appliedWithValues.length) return true;
158
196
 
@@ -252,9 +290,14 @@ export const useProfileFilters = (): {
252
290
  }, [dispatch, setAppliedFilters]);
253
291
 
254
292
  const onApplyFilters = useCallback((): void => {
255
- const validFilters = localFilters.filter(
256
- f => f.value !== '' && f.type != null && f.field != null && f.matchType != null
257
- );
293
+ const validFilters = localFilters.filter(f => {
294
+ // For preset filters, only need type and value
295
+ if (f.type != null && isPresetKey(f.type)) {
296
+ return f.value !== '' && f.type != null;
297
+ }
298
+ // For regular filters, need all fields
299
+ return f.value !== '' && f.type != null && f.field != null && f.matchType != null;
300
+ });
258
301
 
259
302
  const filtersToApply = validFilters.map((f, index) => ({
260
303
  ...f,
@@ -268,6 +311,23 @@ export const useProfileFilters = (): {
268
311
  return convertToProtoFilters(appliedFilters ?? []);
269
312
  }, [appliedFilters]);
270
313
 
314
+ const applyPreset = useCallback(
315
+ (preset: FilterPreset) => {
316
+ const presetFilter: ProfileFilter = {
317
+ id: `filter-preset-${Date.now()}`,
318
+ type: preset.key,
319
+ value: preset.name,
320
+ };
321
+
322
+ // Add preset filter to existing filters
323
+ const updatedFilters = [...localFilters, presetFilter];
324
+ dispatch(setLocalFilters(updatedFilters));
325
+
326
+ setAppliedFilters(updatedFilters);
327
+ },
328
+ [dispatch, setAppliedFilters, localFilters]
329
+ );
330
+
271
331
  return {
272
332
  localFilters,
273
333
  appliedFilters,
@@ -280,5 +340,6 @@ export const useProfileFilters = (): {
280
340
  removeFilter,
281
341
  updateFilter,
282
342
  resetFilters,
343
+ applyPreset,
283
344
  };
284
345
  };
@@ -14,6 +14,8 @@
14
14
  import {useURLStateCustom, type ParamValueSetterCustom} from '@parca/components';
15
15
  import {type ProfileFilter} from '@parca/store';
16
16
 
17
+ import {isPresetKey} from './filterPresets';
18
+
17
19
  // Compact encoding mappings
18
20
  const TYPE_MAP: Record<string, string> = {
19
21
  stack: 's',
@@ -46,8 +48,16 @@ const encodeProfileFilters = (filters: ProfileFilter[]): string => {
46
48
  if (filters.length === 0) return '';
47
49
 
48
50
  return filters
49
- .filter(f => f.value !== '' && f.type != null && f.field != null && f.matchType != null)
51
+ .filter(f => f.value !== '' && f.type != null)
50
52
  .map(f => {
53
+ // Handle preset filters differently
54
+ if (isPresetKey(f.type!)) {
55
+ const presetKey = encodeURIComponent(f.type!);
56
+ const value = encodeURIComponent(f.value);
57
+ return `p:${presetKey}:${value}`;
58
+ }
59
+
60
+ // Handle regular filters
51
61
  const type = TYPE_MAP[f.type!];
52
62
  const field = FIELD_MAP[f.field!];
53
63
  const match = MATCH_MAP[f.matchType!];
@@ -63,7 +73,22 @@ export const decodeProfileFilters = (encoded: string): ProfileFilter[] => {
63
73
 
64
74
  try {
65
75
  return encoded.split(',').map((filter, index) => {
66
- const [type, field, match, ...valueParts] = filter.split(':');
76
+ const parts = filter.split(':');
77
+
78
+ // Handle preset filters (format: p:presetKey:value)
79
+ if (parts[0] === 'p' && parts.length >= 3) {
80
+ const presetKey = decodeURIComponent(parts[1]);
81
+ const value = decodeURIComponent(parts.slice(2).join(':')); // Handle values with colons
82
+
83
+ return {
84
+ id: `filter-${Date.now()}-${index}`,
85
+ type: presetKey,
86
+ value,
87
+ };
88
+ }
89
+
90
+ // Handle regular filters (format: type:field:match:value)
91
+ const [type, field, match, ...valueParts] = parts;
67
92
  const value = decodeURIComponent(valueParts.join(':')); // Handle values with colons
68
93
 
69
94
  return {