mirror of
https://github.com/MadeBaruna/paimon-moe.git
synced 2025-03-23 07:09:33 +01:00
Add recipe page~
This commit is contained in:
parent
0f416d6e35
commit
284442484a
2 changed files with 490 additions and 0 deletions
|
@ -1000,6 +1000,27 @@
|
||||||
"radiantSpincrystal": {
|
"radiantSpincrystal": {
|
||||||
"title": "Radiant Spincrystals"
|
"title": "Radiant Spincrystals"
|
||||||
},
|
},
|
||||||
|
"recipe": {
|
||||||
|
"title": "Recipe",
|
||||||
|
"of": "of",
|
||||||
|
"fastEdit": "Enable this for fast editing",
|
||||||
|
"proficiency": "Proficiency",
|
||||||
|
"collected": "collected",
|
||||||
|
"masteredAll": "You have mastered all owned recipes",
|
||||||
|
"masteredLeft": "recipe can be mastered",
|
||||||
|
"sort": "Show not collected first",
|
||||||
|
"default": "Default",
|
||||||
|
"search": "Search recipe",
|
||||||
|
"missing": "Not yet obtained",
|
||||||
|
"version": "Version",
|
||||||
|
"type": "Type",
|
||||||
|
"searchError": "Cannot find recipe: {query}",
|
||||||
|
"revive": "Revive",
|
||||||
|
"healing": "Healing",
|
||||||
|
"adventurer": "Adventurer",
|
||||||
|
"atk-boost": "ATK-Boosting",
|
||||||
|
"def-boost": "DEF-Boosting"
|
||||||
|
},
|
||||||
"common": {
|
"common": {
|
||||||
"dataSynced": "Data has been synced!",
|
"dataSynced": "Data has been synced!",
|
||||||
"driveError": "Drive sync not available right now 😔",
|
"driveError": "Drive sync not available right now 😔",
|
||||||
|
|
469
src/routes/recipe/index.svelte
Normal file
469
src/routes/recipe/index.svelte
Normal file
|
@ -0,0 +1,469 @@
|
||||||
|
<script context="module">
|
||||||
|
import recipeData from "../../data/recipe/en.json";
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
import { locale, t } from "svelte-i18n";
|
||||||
|
import { onMount, tick } from "svelte";
|
||||||
|
import debounce from "lodash.debounce";
|
||||||
|
import { mdiArrowDown, mdiArrowUp, mdiArrowRight, mdiFilter, mdiCheck, mdiClose } from "@mdi/js";
|
||||||
|
|
||||||
|
import Check from "../../components/Check.svelte";
|
||||||
|
import Checkbox from "../../components/Checkbox.svelte";
|
||||||
|
import { getAccountPrefix } from "../../stores/account";
|
||||||
|
import { readSave, updateSave, fromRemote } from "../../stores/saveManager";
|
||||||
|
import Button from "../../components/Button.svelte";
|
||||||
|
import Icon from "../../components/Icon.svelte";
|
||||||
|
import Select from "../../components/Select.svelte";
|
||||||
|
import { pushToast } from "../../stores/toast";
|
||||||
|
import Ad from "../../components/Ad.svelte";
|
||||||
|
|
||||||
|
let recipeContainer;
|
||||||
|
|
||||||
|
let list = [];
|
||||||
|
let fast = false;
|
||||||
|
let checkList = {};
|
||||||
|
let totalRecipe = 0;
|
||||||
|
let missingMastery = 0;
|
||||||
|
let obtainedRecipeCount = 0;
|
||||||
|
|
||||||
|
let active = "0";
|
||||||
|
let activeIndex = 0;
|
||||||
|
let categories = [];
|
||||||
|
|
||||||
|
let originalList = [];
|
||||||
|
let sortedRecipes = Object.entries(recipeData);
|
||||||
|
|
||||||
|
let showFilter = false;
|
||||||
|
let nameFilter = "";
|
||||||
|
let sort = false;
|
||||||
|
|
||||||
|
const typeIcon = {
|
||||||
|
"Revive": "revive",
|
||||||
|
"Healing": "healing",
|
||||||
|
"Adventurer": "adventurer",
|
||||||
|
"ATK-Boosting": "atk_boosting",
|
||||||
|
"DEF-Boosting": "def_boosting"
|
||||||
|
};
|
||||||
|
|
||||||
|
const rarityClass = {
|
||||||
|
1: "bg-white",
|
||||||
|
2: "bg-green-400",
|
||||||
|
3: "bg-primary",
|
||||||
|
4: "bg-rare-from",
|
||||||
|
5: "bg-legendary-from"
|
||||||
|
};
|
||||||
|
|
||||||
|
const versions = [
|
||||||
|
"1.0",
|
||||||
|
"1.1",
|
||||||
|
"1.2",
|
||||||
|
"1.3",
|
||||||
|
"1.4",
|
||||||
|
"1.5",
|
||||||
|
"1.6",
|
||||||
|
"2.0",
|
||||||
|
"2.1",
|
||||||
|
"2.2",
|
||||||
|
"2.3",
|
||||||
|
"2.4",
|
||||||
|
"2.5",
|
||||||
|
"2.6",
|
||||||
|
"2.7",
|
||||||
|
"2.8",
|
||||||
|
"3.0",
|
||||||
|
"3.1",
|
||||||
|
"3.2",
|
||||||
|
"3.3",
|
||||||
|
"3.4"
|
||||||
|
].map(e => ({
|
||||||
|
label: e,
|
||||||
|
value: e
|
||||||
|
}));
|
||||||
|
let versionFilter = [];
|
||||||
|
|
||||||
|
const types = [
|
||||||
|
{ label: $t("recipe.revive"), value: "Revive", image: "/images/recipe/type/revive.png" },
|
||||||
|
{ label: $t("recipe.healing"), value: "Healing", image: "/images/recipe/type/healing.png" },
|
||||||
|
{ label: $t("recipe.adventurer"), value: "Adventurer", image: "/images/recipe/type/adventurer.png" },
|
||||||
|
{ label: $t("recipe.atk-boost"), value: "ATK-Boosting", image: "/images/recipe/type/atk_boosting.png" },
|
||||||
|
{ label: $t("recipe.def-boost"), value: "DEF-Boosting", image: "/images/recipe/type/def_boosting.png" }
|
||||||
|
];
|
||||||
|
let typeFilter = [];
|
||||||
|
|
||||||
|
function parseCategories() {
|
||||||
|
totalRecipe = 0;
|
||||||
|
obtainedRecipeCount = 0;
|
||||||
|
missingMastery = 0;
|
||||||
|
|
||||||
|
categories = Object.entries(recipeData)
|
||||||
|
.map(([id, data]) => ({
|
||||||
|
id,
|
||||||
|
name: data.name,
|
||||||
|
...data.recipes.reduce(
|
||||||
|
(prev, recipe) => {
|
||||||
|
if (checkList[id] === undefined) checkList[id] = {};
|
||||||
|
if (checkList[id][recipe.id] === undefined && recipe.default) checkList[id][recipe.id] = 0;
|
||||||
|
|
||||||
|
const obtained = checkList[id][recipe.id] !== undefined;
|
||||||
|
const mastered = checkList[id][recipe.id] === recipe.proficiency;
|
||||||
|
|
||||||
|
prev.total += recipe.default ? 0 : 1;
|
||||||
|
totalRecipe += recipe.default ? 0 : 1;
|
||||||
|
|
||||||
|
prev.obtained += obtained && !recipe.default ? 1 : 0;
|
||||||
|
obtainedRecipeCount += obtained && !recipe.default ? 1 : 0;
|
||||||
|
|
||||||
|
prev.missingMastery += obtained && !mastered ? 1 : 0;
|
||||||
|
missingMastery += obtained && !mastered ? 1 : 0;
|
||||||
|
|
||||||
|
return prev;
|
||||||
|
},
|
||||||
|
{ obtained: 0, total: 0, missingMastery: 0 }
|
||||||
|
)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function orderRecipe() {
|
||||||
|
if (!sort) {
|
||||||
|
if (originalList.length === 0) return;
|
||||||
|
list = originalList;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
originalList = list.slice();
|
||||||
|
list = list.sort(sortRecipe);
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveData = debounce(async () => {
|
||||||
|
const data = checkList;
|
||||||
|
const prefix = getAccountPrefix();
|
||||||
|
await updateSave(`${prefix}recipe`, data);
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
|
const search = debounce(async () => {
|
||||||
|
if (nameFilter === "") {
|
||||||
|
await changeCategory(active, activeIndex, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const query = nameFilter.toLowerCase();
|
||||||
|
|
||||||
|
for (const recipe of recipeData[active].recipes) {
|
||||||
|
if (!recipe.name.toLowerCase().includes(query)) continue;
|
||||||
|
await changeCategory(active, activeIndex, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let index = 0;
|
||||||
|
for (const [id, item] of sortedRecipes) {
|
||||||
|
for (const recipe of item.recipes) {
|
||||||
|
if (!recipe.name.toLowerCase().includes(query)) continue;
|
||||||
|
await changeCategory(id, index, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
await changeCategory(active, activeIndex, true);
|
||||||
|
pushToast($t("recipe.searchError", { values: { query } }), "error");
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
const updateSelectFilter = debounce(() => changeCategory(active, activeIndex, true), 500);
|
||||||
|
|
||||||
|
async function changeCategory(id, index, firstLoad) {
|
||||||
|
active = id;
|
||||||
|
activeIndex = index;
|
||||||
|
|
||||||
|
const filterVersion = versionFilter.length > 0;
|
||||||
|
const filteredVersion = versionFilter.map(e => e.value);
|
||||||
|
|
||||||
|
let filterType = [];
|
||||||
|
for (const e of typeFilter) filterType.push(e.value);
|
||||||
|
|
||||||
|
const filterName = nameFilter !== "";
|
||||||
|
const query = nameFilter.toLowerCase();
|
||||||
|
|
||||||
|
if (checkList[active] === undefined) checkList[active] = {};
|
||||||
|
|
||||||
|
list = recipeData[active].recipes.filter(e => {
|
||||||
|
if (filterVersion && !filteredVersion.includes(e.ver)) return false;
|
||||||
|
if (filterType.length > 0 && !filterType.includes(e.type)) return false;
|
||||||
|
return !(filterName && !e.name.toLowerCase().includes(query));
|
||||||
|
}).map(e => {
|
||||||
|
e.mastered = checkList[active][e.id] === e.proficiency;
|
||||||
|
e.obtained = checkList[active][e.id] !== undefined;
|
||||||
|
e.userProf = checkList[active][e.id];
|
||||||
|
e.topLimit = e.userProf === e.proficiency - 1;
|
||||||
|
e.botLimit = e.userProf === 0;
|
||||||
|
e.rarityClass = rarityClass[e.rarity];
|
||||||
|
return e;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (sort) {
|
||||||
|
originalList = list.slice();
|
||||||
|
list = list.sort(sortRecipe);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstLoad) return;
|
||||||
|
await tick();
|
||||||
|
recipeContainer.scrollIntoView({ behavior: "smooth" });
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortRecipe(a, b) {
|
||||||
|
return a.mastered && b.mastered ? 0 : b.mastered ? -1 : a.mastered ? 1 : a.obtained && b.obtained ? 0 : b.obtained ? -1 : a.obtained ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleOn(index) {
|
||||||
|
set(index, fast ? list[index].proficiency : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleOff(index) {
|
||||||
|
set(index, fast ? -1 : list[index].proficiency - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function increment(index) {
|
||||||
|
set(index, fast ? list[index].proficiency : list[index].userProf + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function decrement(index) {
|
||||||
|
set(index, fast ? -1 : list[index].userProf - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function set(index, val) {
|
||||||
|
let wasObtained = list[index].obtained;
|
||||||
|
let wasMastered = list[index].mastered;
|
||||||
|
let min = list[index].default ? 0 : undefined;
|
||||||
|
|
||||||
|
val = val < 0 || val === undefined ? min : val;
|
||||||
|
checkList[active][list[index].id] = val;
|
||||||
|
list[index].userProf = val;
|
||||||
|
list[index].botLimit = val === 0;
|
||||||
|
list[index].topLimit = val === list[index].proficiency - 1;
|
||||||
|
|
||||||
|
let isObtained = val !== undefined;
|
||||||
|
let isMastered = val === list[index].proficiency;
|
||||||
|
|
||||||
|
let cObtained = isObtained !== wasObtained;
|
||||||
|
let cMastered = isMastered !== wasMastered;
|
||||||
|
let oVal = isObtained ? 1 : -1;
|
||||||
|
let mVal = isMastered ? -1 : 1;
|
||||||
|
|
||||||
|
list[index].obtained = isObtained;
|
||||||
|
list[index].mastered = isMastered;
|
||||||
|
|
||||||
|
categories[activeIndex].obtained += cObtained ? oVal : 0;
|
||||||
|
obtainedRecipeCount += cObtained ? oVal : 0;
|
||||||
|
|
||||||
|
categories[activeIndex].missingMastery += cObtained ? cMastered ? 0 : oVal : (cMastered ? mVal : 0);
|
||||||
|
missingMastery += cObtained ? cMastered ? 0 : oVal : (cMastered ? mVal : 0);
|
||||||
|
|
||||||
|
saveData();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function changeLocale(locale) {
|
||||||
|
const data = await import(`../../data/recipe/${locale}.json`);
|
||||||
|
recipeData = data.default;
|
||||||
|
sortedRecipes = Object.entries(recipeData);
|
||||||
|
await changeCategory(active, activeIndex, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readLocalData() {
|
||||||
|
const prefix = getAccountPrefix();
|
||||||
|
const recipeData = await readSave(`${prefix}recipe`);
|
||||||
|
if (recipeData !== null) checkList = recipeData;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function process() {
|
||||||
|
await readLocalData();
|
||||||
|
parseCategories();
|
||||||
|
await changeCategory("0", 0, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
await process();
|
||||||
|
locale.subscribe(changeLocale);
|
||||||
|
});
|
||||||
|
|
||||||
|
$: if ($fromRemote) {
|
||||||
|
console.log("update from google drive");
|
||||||
|
process();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>Recipes - Paimon.moe</title>
|
||||||
|
<meta name="description" content="Track your Genshin Impact recipe easily" />
|
||||||
|
<meta property="og:description" content="Track your Genshin Impact recipe easily" />
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<div class="flex">
|
||||||
|
<div class="lg:ml-64 pt-20 px-4 lg:px-8 lg:pt-8 max-w-screen-xl w-full">
|
||||||
|
<div class="flex flex-col md:flex-row items-center space-y-2 md:space-y-0 md:space-x-2 mb-2 md:mb-0">
|
||||||
|
<h1 class="font-display font-black text-3xl md:text-4xl text-white">{$t('recipe.title')}</h1>
|
||||||
|
<div class="flex space-x-2">
|
||||||
|
<p class="text-gray-400 text-xl rounded-xl bg-black bg-opacity-50 px-2 py-1 flex items-center">
|
||||||
|
{obtainedRecipeCount} {$t('recipe.of')} {totalRecipe} {$t('recipe.collected')}
|
||||||
|
<img src="/images/recipe/recipe.png" class="w-7 h-7 ml-1" alt="recipe" />
|
||||||
|
</p>
|
||||||
|
<div class="text-gray-400 text-xl rounded-xl bg-black bg-opacity-50 px-2 py-1">
|
||||||
|
{#if missingMastery === 0}
|
||||||
|
<p>{$t('recipe.masteredAll')}</p>
|
||||||
|
{:else}
|
||||||
|
<p>{missingMastery} {$t('recipe.masteredLeft')}</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex space-x-2 items-center">
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
on:click={() => {
|
||||||
|
showFilter = !showFilter;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon path={mdiFilter} color="white" />
|
||||||
|
</Button>
|
||||||
|
<div class="pl-4 text-white">
|
||||||
|
<Checkbox checked={sort} on:change={() => orderRecipe(sort = !sort)}>{$t('recipe.sort')}</Checkbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{#if showFilter}
|
||||||
|
<div class="mb-2 flex flex-col md:flex-row space-y-2 md:space-y-0 md:space-x-2">
|
||||||
|
<div
|
||||||
|
class="flex flex-1 relative items-center bg-background rounded-2xl h-14 focus-within:border-primary border-2 border-transparent ease-in duration-100"
|
||||||
|
style="min-height: 3.5rem;"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
placeholder={$t('recipe.search')}
|
||||||
|
bind:value={nameFilter}
|
||||||
|
on:input={search}
|
||||||
|
class="pl-4 w-full min-h-full pr-4 text-white placeholder-gray-500 leading-none bg-transparent border-none focus:outline-none"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Select
|
||||||
|
multiselect
|
||||||
|
options={versions}
|
||||||
|
bind:selected={versionFilter}
|
||||||
|
placeholder={$t('recipe.version')}
|
||||||
|
className="w-full md:w-40"
|
||||||
|
on:change={updateSelectFilter}
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
multiselect
|
||||||
|
image={true}
|
||||||
|
options={types}
|
||||||
|
bind:selected={typeFilter}
|
||||||
|
placeholder={$t('recipe.type')}
|
||||||
|
className="w-full md:w-56"
|
||||||
|
on:change={updateSelectFilter}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<div class="flex flex-col lg:flex-row space-y-3 lg:space-y-0 lg:space-x-3">
|
||||||
|
<div class="flex flex-col space-y-2 lg:h-screen lg:overflow-auto lg:sticky lg:pr-1 pb-4 category">
|
||||||
|
{#each categories as category, index (category.id)}
|
||||||
|
<div
|
||||||
|
class="rounded-xl p-2 cursor-pointer flex flex-col {category.id === active ? 'bg-primary' : 'bg-item'}"
|
||||||
|
on:click={() => changeCategory(category.id, index)}
|
||||||
|
>
|
||||||
|
<p class="font-semibold {category.id === active ? 'text-black' : 'text-white'}">{category.name}</p>
|
||||||
|
<div class="flex">
|
||||||
|
<p class="flex-1 {category.id === active ? 'text-gray-900' : 'text-gray-400'}">
|
||||||
|
{category.obtained}/{category.total}
|
||||||
|
</p>
|
||||||
|
<p class={category.id === active ? 'text-gray-900' : 'text-gray-400'}>
|
||||||
|
{category.missingMastery}
|
||||||
|
</p>
|
||||||
|
<img src="/images/wish.png" class="w-6 h-6 ml-1" alt="wish" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
<div>
|
||||||
|
<div class="mt-5 text-white flex justify-center">
|
||||||
|
{$t('recipe.fastEdit')}
|
||||||
|
<Icon className="mx-2" size={1} path={mdiArrowRight} />
|
||||||
|
<Checkbox checked={fast} on:change={() => fast = !fast}></Checkbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col space-y-2 flex-1 lg:pt-2" bind:this={recipeContainer}>
|
||||||
|
{#each list as el, index}
|
||||||
|
<div class="bg-item rounded-xl px-1 py-1 text-white">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="bg-opacity-50 rounded-xl overflow-hidden {el.rarityClass}">
|
||||||
|
<img width="56" height="56" src="/images/recipe/food/{el.id}.png" alt={el.id} loading="lazy" />
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 pr-1 pl-2">
|
||||||
|
<p class="font-semibold">
|
||||||
|
<img class="w-6 inline-block" src="/images/recipe/type/{typeIcon[el.type]}.png" alt={el.type} loading="lazy" />
|
||||||
|
{el.name}
|
||||||
|
<span class="ml-1 rounded-xl bg-background px-2 text-gray-400 text-sm font-normal select-none">
|
||||||
|
{el.ver}
|
||||||
|
</span>
|
||||||
|
{#if el.default}
|
||||||
|
<span class="ml-1 rounded-xl bg-background px-2 text-gray-400 text-sm font-normal select-none">
|
||||||
|
{$t("recipe.default")}
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
</p>
|
||||||
|
<p class="text-gray-400">{el.location}</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center">
|
||||||
|
{#if el.userProf === undefined}
|
||||||
|
<p class="mr-1">{$t("recipe.missing")}</p>
|
||||||
|
<Check checked={el.obtained} on:change={() => toggleOn(index)} inverted />
|
||||||
|
{:else if el.mastered}
|
||||||
|
<Check checked={el.obtained} on:change={() => toggleOff(index)} inverted />
|
||||||
|
{:else}
|
||||||
|
<p class="mr-1">{$t("recipe.proficiency")}: {el.userProf}/{el.proficiency}</p>
|
||||||
|
<div class="ml-3 mr-1">
|
||||||
|
<Button color={el.topLimit ? 'green' : 'blue'} on:click={() => increment(index)} size="sm">
|
||||||
|
<Icon size={1} path={el.topLimit ? mdiCheck : mdiArrowUp} />
|
||||||
|
</Button>
|
||||||
|
<Button disabled="{el.default && el.botLimit}" color={!el.default && el.botLimit ? 'red' : 'blue'} checked={el.obtained} on:click={() => decrement(index)} size="sm">
|
||||||
|
<Icon size={1} path={el.botLimit ? mdiClose : mdiArrowDown} />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="fixed top-0 right-0 m-8">
|
||||||
|
<Ad class="ml-4" type="desktop" variant="mpu" id="1" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Ad type="desktop" variant="lb" id="2" />
|
||||||
|
<Ad type="mobile" variant="lb" id="1" />
|
||||||
|
|
||||||
|
<style lang="postcss">
|
||||||
|
.category {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@screen lg {
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
@apply bg-transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(0, 0, 0, 0.35);
|
||||||
|
@apply rounded-xl;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category {
|
||||||
|
min-width: 20rem;
|
||||||
|
width: 20rem;
|
||||||
|
top: 0;
|
||||||
|
padding-top: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
Loading…
Add table
Reference in a new issue