Răsfoiți Sursa

release v0.2.3

Harlan Iverson 1 an în urmă
părinte
comite
1f3bc0a929

+ 5 - 17
hogumathi_app.py

@@ -6,6 +6,10 @@ from configparser import ConfigParser
 from flask import Flask, g, redirect, url_for, render_template, jsonify
 from flask import Flask, g, redirect, url_for, render_template, jsonify
 from flask_cors import CORS
 from flask_cors import CORS
 
 
+from dotenv import load_dotenv
+
+load_dotenv()
+
 sys.path.append('.data/extensions')
 sys.path.append('.data/extensions')
 
 
 if find_spec('twitter_v2_facade'):
 if find_spec('twitter_v2_facade'):
@@ -87,26 +91,10 @@ else:
 
 
 add_account_enabled = True
 add_account_enabled = True
 
 
-def import_env ():
-    cp = ConfigParser()
-    if os.path.exists('.env'):
-        with open('.env') as stream:
-            cp.read_string('[default]\n' + stream.read())
-            os.environ.update(dict(cp['default']))
-
-
-
 if __name__ == '__main__':
 if __name__ == '__main__':
     glitch_enabled = os.environ.get('PROJECT_DOMAIN') and True
     glitch_enabled = os.environ.get('PROJECT_DOMAIN') and True
     
     
-    if glitch_enabled:
-        t = os.environ.get('BEARER_TOKEN')
-        if t:
-            t = t.replace('%%', '%')
-            os.environ['BEARER_TOKEN'] = t
-            
-    else:
-        import_env()
+    t = os.environ.get('BEARER_TOKEN')
 
 
     PORT = int(os.environ.get('PORT', 5000))
     PORT = int(os.environ.get('PORT', 5000))
     HOST = os.environ.get('HOST', '127.0.0.1')
     HOST = os.environ.get('HOST', '127.0.0.1')

+ 269 - 0
item_collections.py

@@ -0,0 +1,269 @@
+from dataclasses import asdict, replace
+
+from importlib.util import find_spec
+
+import os
+import json
+
+
+from flask import request, g, jsonify, render_template,  Blueprint, url_for, session
+
+from tweet_source import ApiV2TweetSource
+from view_model import FeedItem, cleandict
+
+twitter_enabled = False
+if find_spec('twitter_v2_facade'):
+    from twitter_v2_facade import tweet_model_dc_vm
+    twitter_enabled = True
+    
+youtube_enabled = False
+if find_spec('youtube_facade'):
+    from youtube_facade import youtube_model, get_youtube_builder
+    youtube_enabled = True
+
+DATA_DIR=".data"
+
+item_collections_bp = Blueprint('item_collections', 'item_collections',
+    static_folder='static',
+    static_url_path='',
+    url_prefix='/')
+
+
+def get_tweet_collection (collection_id):
+    with open(f'{DATA_DIR}/collection/{collection_id}.json', 'rt', encoding='utf-8') as f:
+        collection = json.loads(f.read())
+        
+    return collection
+
+def collection_from_card_source (url):
+    """
+    temp1 = await fetch('http://localhost:5000/notes/cards/search?q=twitter.com/&limit=10').then(r => r.json())
+
+    re = /(http[^\s]+twitter\.com\/[^\/]+\/status\/[\d]+)/ig
+    tweetLinks = temp1.cards.map(c => c.card.content).map(c => c.match(re))
+    tweetLinks2 = tweetLinks.flat().filter(l => l)
+    tweetLinksS = Array.from(new Set(tweetLinks2))
+
+    statusUrls = tweetLinksS.map(s => new URL(s))
+    //users = Array.from(new Set(statusUrls.map(s => s.pathname.split('/')[1])))
+    ids = Array.from(new Set(statusUrls.map(s => parseInt(s.pathname.split('/')[3]))))
+    """
+    
+    """
+    temp1 = JSON.parse(document.body.innerText)
+    // get swipe note + created_at + tweet user + tweet ID
+    tweetCards = temp1.cards.map(c => c.card).filter(c => c.content.match(re))
+    tweets = tweetCards.map(c => ({created_at: c.created_at, content: c.content, tweets: c.content.match(re).map(m => new URL(m))}))
+    
+    tweets.filter(t => t.tweets.filter(t2 => t2.user.toLowerCase() == 'stephenmpinto').length)
+    
+    
+    // HN
+    re = /(http[^\s]+news.ycombinator\.com\/[^\s]+\=[\d]+)/ig
+    linkCards = temp1.cards.map(c => c.card).filter(c => c.content.match(re))
+    links = linkCards.map(c => ({created_at: c.created_at, content: c.content, links: c.content.match(re).map(m => new URL(m))}))
+    
+    // YT (I thnk I've already done this one)
+    
+    """
+    
+    # more in 2022 twitter report
+
+    return None
+
+
+def expand_item (item, me, tweets = None, includes = None, yt_videos = None):
+    if 'id' in item:
+        t = list(filter(lambda t: item['id'] == t.id, tweets))
+        
+        if not len(t):
+            print("no tweet for item: " + item['id'])
+            feed_item = FeedItem(
+                id = item['id'],
+                text = "(Deleted, suspended or blocked)",
+                created_at = "",
+                handle = "error",
+                display_name = "Error"
+            )
+            # FIXME 1) put this in relative order to the collection
+            # FIXME 2) we can use the tweet link to get the user ID...
+            
+        else:
+            t = t[0]
+        
+            feed_item = tweet_model_dc_vm(includes, t, me)
+
+            note = item.get('note')
+            feed_item = replace(feed_item, note = note)
+            
+    elif 'yt_id' in item:
+        yt_id = item['yt_id']
+        
+        vid = list(filter(lambda v: v['id'] == yt_id, yt_videos))[0]
+        feed_item = youtube_model(vid)
+    
+        note = item.get('note')
+        feed_item.update({'note': note})
+    
+        
+    
+    return feed_item
+
+
+
+# pagination token is the next tweet_ID
+@item_collections_bp.get('/collection/<collection_id>.html')
+def get_collection_html (collection_id):
+    me = request.args.get('me')
+    acct = session.get(me)
+    
+    max_results = int(request.args.get('max_results', 10))
+    
+    pagination_token = int(request.args.get('pagination_token', 0))
+    
+    collection = get_tweet_collection(collection_id)
+    
+    if 'authorized_users' in collection and (not acct or not me in collection['authorized_users']):
+        return 'access denied.', 403
+    
+    items = collection['items'][pagination_token:(pagination_token + max_results)]
+    
+    if not len(items):
+        return 'no tweets', 404
+    
+    twitter_token = os.environ.get('BEARER_TOKEN')
+    if me and me.startswith('twitter:') and acct:
+        twitter_token = acct['access_token']
+    
+    tweet_source = ApiV2TweetSource(twitter_token)
+    
+    tweet_ids = filter(lambda i: 'id' in i, items)
+    tweet_ids = list(map(lambda item: item['id'], tweet_ids))
+    tweets_response = tweet_source.get_tweets( tweet_ids, return_dataclass=True )
+    
+    
+    
+    yt_ids = filter(lambda i: 'yt_id' in i, items)
+    yt_ids = list(map(lambda item: item['yt_id'], yt_ids))
+    youtube = get_youtube_builder()
+    
+    videos_response = youtube.videos().list(id=','.join(yt_ids), part='snippet,contentDetails,liveStreamingDetails,statistics,recordingDetails', maxResults=1).execute()
+    
+    
+    #print(response_json)
+    if tweets_response.errors:
+        # types:
+        # https://api.twitter.com/2/problems/not-authorized-for-resource (blocked or suspended)
+        # https://api.twitter.com/2/problems/resource-not-found (deleted)
+        #print(response_json.get('errors'))
+        for err in tweets_response.errors:
+            if not 'type' in err:
+                print('unknown error type: ' + str(err))
+            elif err['type'] == 'https://api.twitter.com/2/problems/not-authorized-for-resource':
+                print('blocked or suspended tweet: ' + err['value'])
+            elif err['type'] == 'https://api.twitter.com/2/problems/resource-not-found':
+                print('deleted tweet: ' + err['value'])
+            else:
+                print('unknown error')
+            
+            print(json.dumps(err, indent=2))
+            
+    
+    includes = tweets_response.includes
+    tweets = tweets_response.data
+    
+    feed_items = list(map(lambda item: expand_item(item, me, tweets, includes, videos_response['items']), items))
+    
+    if request.args.get('format') == 'json':
+        return jsonify({'ids': tweet_ids,
+                       'tweets': cleandict(asdict(tweets_response)),
+                       'feed_items': feed_items,
+                       'items': items,
+                       'pagination_token': pagination_token})
+    else:
+        query = {}
+        
+        if pagination_token:
+            query['next_data_url'] = url_for('.get_collection_html', collection_id=collection_id, pagination_token=pagination_token)
+        
+        if 'HX-Request' in request.headers:
+            return render_template('partial/tweets-timeline.html', tweets = feed_items, user = {}, query = query)
+        else:
+            if pagination_token:
+                query['next_page_url'] = url_for('.get_collection_html', collection_id=collection_id, pagination_token=pagination_token)
+            return render_template('tweet-collection.html', tweets = feed_items, user = {}, query = query)
+
+# pagination token is the next tweet_ID
+@item_collections_bp.get('/collections.html')
+def get_collections_html ():
+    me = request.args.get('me')
+    acct = session.get(me)
+    
+    collections = []
+    with os.scandir('.data/collection') as collections_files:
+        for collection_file in collections_files:
+            if not collection_file.name.endswith('.json'):
+                continue
+            
+            with open(collection_file.path, 'rt', encoding='utf-8') as f:
+                coll = json.load(f)
+                if 'authorized_users' in coll and (not acct or not me in coll['authorized_users']):
+                    continue
+                    
+                collection_id = collection_file.name[:-len('.json')]
+                
+                coll_info = dict(
+                    collection_id = collection_id,
+                    href = url_for('.get_collection_html', collection_id=collection_id)
+                )
+                
+                collections.append(coll_info)
+    
+    return jsonify(collections)
+
+
+@item_collections_bp.post('/data/collection/create/from-cards')
+def post_data_collection_create_from_cards ():
+    """
+    // create collection from search, supporting multiple Tweets per card and Tweets in multiple Cards.
+    
+    
+    re = /(https?[a-z0-9\.\/\:]+twitter\.com\/[0-9a-z\_]+\/status\/[\d]+)/ig
+    
+    temp1 = await fetch('http://localhost:5000/notes/cards/search?q=twitter.com/').then(r => r.json())
+
+    cardMatches = temp1.cards
+    .map(cm => Object.assign({}, cm, {tweetLinks: Array.from(new Set(cm.card.content.match(re)))}))
+    .filter(cm => cm.tweetLinks && cm.tweetLinks.length)
+    .map(cm => Object.assign({}, cm, {tweetUrls: cm.tweetLinks.map(l => new URL(l))}))
+    .map(cm => Object.assign({}, cm, {tweetInfos: cm.tweetUrls.map(u => ({user: u.pathname.split('/')[1], tweetId: u.pathname.split('/')[3]}))}));
+    
+    collectionCards = {}
+
+    cardMatches.forEach(function (cm) {
+        if (!cm.tweetLinks.length) { return; }
+        cm.tweetInfos.forEach(function (ti) {
+            if (!collectionCards[ti.tweetId]) {
+                collectionCards[ti.tweetId] = [];
+            }
+            collectionCards[ti.tweetId].push(cm.card);
+        })
+    })
+
+    var collectionItems = [];
+    Object.entries(collectionCards).forEach(function (e) {
+        var tweetId = e[0], cards = e[1];
+        var note = cards.map(function (card) {
+            return card.created_at + "\n\n" + card.content;
+        }).join("\n\n-\n\n");
+
+        collectionItems.push({id: tweetId, note: note, tweet_infos: cm.tweetInfos, card_infos: cards.map(c => 'card#' + c.id)});
+    })
+    """
+    
+    collection = {
+        'items': [], # described in JS function above
+        'authorized_users': [g.twitter_user['id']]
+    }
+    
+    return jsonify(collection)

+ 1 - 0
requirements.txt

@@ -4,6 +4,7 @@ Flask-Cors                ~= 3.0.10
 Flask-Session             ~= 0.4.0
 Flask-Session             ~= 0.4.0
 json-stream               ~= 1.3.0
 json-stream               ~= 1.3.0
 python-dateutil           ~= 2.8.2
 python-dateutil           ~= 2.8.2
+python-dotenv             ~= 0.21.0
 requests                  ~= 2.28.0
 requests                  ~= 2.28.0
 requests-oauthlib         ~= 1.3.1
 requests-oauthlib         ~= 1.3.1
 google-api-python-client  ~= 2.70.0
 google-api-python-client  ~= 2.70.0

+ 2018 - 0
static/bootstrap-icons.css

@@ -0,0 +1,2018 @@
+@font-face {
+  font-display: block;
+  font-family: "bootstrap-icons";
+  src: url("./bootstrap-icons.woff2?24e3eb84d0bcaf83d77f904c78ac1f47") format("woff2"),
+url("./bootstrap-icons.woff?24e3eb84d0bcaf83d77f904c78ac1f47") format("woff");
+}
+
+.bi::before,
+[class^="bi-"]::before,
+[class*=" bi-"]::before {
+  display: inline-block;
+  font-family: bootstrap-icons !important;
+  font-style: normal;
+  font-weight: normal !important;
+  font-variant: normal;
+  text-transform: none;
+  line-height: 1;
+  vertical-align: -.125em;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+.bi-123::before { content: "\f67f"; }
+.bi-alarm-fill::before { content: "\f101"; }
+.bi-alarm::before { content: "\f102"; }
+.bi-align-bottom::before { content: "\f103"; }
+.bi-align-center::before { content: "\f104"; }
+.bi-align-end::before { content: "\f105"; }
+.bi-align-middle::before { content: "\f106"; }
+.bi-align-start::before { content: "\f107"; }
+.bi-align-top::before { content: "\f108"; }
+.bi-alt::before { content: "\f109"; }
+.bi-app-indicator::before { content: "\f10a"; }
+.bi-app::before { content: "\f10b"; }
+.bi-archive-fill::before { content: "\f10c"; }
+.bi-archive::before { content: "\f10d"; }
+.bi-arrow-90deg-down::before { content: "\f10e"; }
+.bi-arrow-90deg-left::before { content: "\f10f"; }
+.bi-arrow-90deg-right::before { content: "\f110"; }
+.bi-arrow-90deg-up::before { content: "\f111"; }
+.bi-arrow-bar-down::before { content: "\f112"; }
+.bi-arrow-bar-left::before { content: "\f113"; }
+.bi-arrow-bar-right::before { content: "\f114"; }
+.bi-arrow-bar-up::before { content: "\f115"; }
+.bi-arrow-clockwise::before { content: "\f116"; }
+.bi-arrow-counterclockwise::before { content: "\f117"; }
+.bi-arrow-down-circle-fill::before { content: "\f118"; }
+.bi-arrow-down-circle::before { content: "\f119"; }
+.bi-arrow-down-left-circle-fill::before { content: "\f11a"; }
+.bi-arrow-down-left-circle::before { content: "\f11b"; }
+.bi-arrow-down-left-square-fill::before { content: "\f11c"; }
+.bi-arrow-down-left-square::before { content: "\f11d"; }
+.bi-arrow-down-left::before { content: "\f11e"; }
+.bi-arrow-down-right-circle-fill::before { content: "\f11f"; }
+.bi-arrow-down-right-circle::before { content: "\f120"; }
+.bi-arrow-down-right-square-fill::before { content: "\f121"; }
+.bi-arrow-down-right-square::before { content: "\f122"; }
+.bi-arrow-down-right::before { content: "\f123"; }
+.bi-arrow-down-short::before { content: "\f124"; }
+.bi-arrow-down-square-fill::before { content: "\f125"; }
+.bi-arrow-down-square::before { content: "\f126"; }
+.bi-arrow-down-up::before { content: "\f127"; }
+.bi-arrow-down::before { content: "\f128"; }
+.bi-arrow-left-circle-fill::before { content: "\f129"; }
+.bi-arrow-left-circle::before { content: "\f12a"; }
+.bi-arrow-left-right::before { content: "\f12b"; }
+.bi-arrow-left-short::before { content: "\f12c"; }
+.bi-arrow-left-square-fill::before { content: "\f12d"; }
+.bi-arrow-left-square::before { content: "\f12e"; }
+.bi-arrow-left::before { content: "\f12f"; }
+.bi-arrow-repeat::before { content: "\f130"; }
+.bi-arrow-return-left::before { content: "\f131"; }
+.bi-arrow-return-right::before { content: "\f132"; }
+.bi-arrow-right-circle-fill::before { content: "\f133"; }
+.bi-arrow-right-circle::before { content: "\f134"; }
+.bi-arrow-right-short::before { content: "\f135"; }
+.bi-arrow-right-square-fill::before { content: "\f136"; }
+.bi-arrow-right-square::before { content: "\f137"; }
+.bi-arrow-right::before { content: "\f138"; }
+.bi-arrow-up-circle-fill::before { content: "\f139"; }
+.bi-arrow-up-circle::before { content: "\f13a"; }
+.bi-arrow-up-left-circle-fill::before { content: "\f13b"; }
+.bi-arrow-up-left-circle::before { content: "\f13c"; }
+.bi-arrow-up-left-square-fill::before { content: "\f13d"; }
+.bi-arrow-up-left-square::before { content: "\f13e"; }
+.bi-arrow-up-left::before { content: "\f13f"; }
+.bi-arrow-up-right-circle-fill::before { content: "\f140"; }
+.bi-arrow-up-right-circle::before { content: "\f141"; }
+.bi-arrow-up-right-square-fill::before { content: "\f142"; }
+.bi-arrow-up-right-square::before { content: "\f143"; }
+.bi-arrow-up-right::before { content: "\f144"; }
+.bi-arrow-up-short::before { content: "\f145"; }
+.bi-arrow-up-square-fill::before { content: "\f146"; }
+.bi-arrow-up-square::before { content: "\f147"; }
+.bi-arrow-up::before { content: "\f148"; }
+.bi-arrows-angle-contract::before { content: "\f149"; }
+.bi-arrows-angle-expand::before { content: "\f14a"; }
+.bi-arrows-collapse::before { content: "\f14b"; }
+.bi-arrows-expand::before { content: "\f14c"; }
+.bi-arrows-fullscreen::before { content: "\f14d"; }
+.bi-arrows-move::before { content: "\f14e"; }
+.bi-aspect-ratio-fill::before { content: "\f14f"; }
+.bi-aspect-ratio::before { content: "\f150"; }
+.bi-asterisk::before { content: "\f151"; }
+.bi-at::before { content: "\f152"; }
+.bi-award-fill::before { content: "\f153"; }
+.bi-award::before { content: "\f154"; }
+.bi-back::before { content: "\f155"; }
+.bi-backspace-fill::before { content: "\f156"; }
+.bi-backspace-reverse-fill::before { content: "\f157"; }
+.bi-backspace-reverse::before { content: "\f158"; }
+.bi-backspace::before { content: "\f159"; }
+.bi-badge-3d-fill::before { content: "\f15a"; }
+.bi-badge-3d::before { content: "\f15b"; }
+.bi-badge-4k-fill::before { content: "\f15c"; }
+.bi-badge-4k::before { content: "\f15d"; }
+.bi-badge-8k-fill::before { content: "\f15e"; }
+.bi-badge-8k::before { content: "\f15f"; }
+.bi-badge-ad-fill::before { content: "\f160"; }
+.bi-badge-ad::before { content: "\f161"; }
+.bi-badge-ar-fill::before { content: "\f162"; }
+.bi-badge-ar::before { content: "\f163"; }
+.bi-badge-cc-fill::before { content: "\f164"; }
+.bi-badge-cc::before { content: "\f165"; }
+.bi-badge-hd-fill::before { content: "\f166"; }
+.bi-badge-hd::before { content: "\f167"; }
+.bi-badge-tm-fill::before { content: "\f168"; }
+.bi-badge-tm::before { content: "\f169"; }
+.bi-badge-vo-fill::before { content: "\f16a"; }
+.bi-badge-vo::before { content: "\f16b"; }
+.bi-badge-vr-fill::before { content: "\f16c"; }
+.bi-badge-vr::before { content: "\f16d"; }
+.bi-badge-wc-fill::before { content: "\f16e"; }
+.bi-badge-wc::before { content: "\f16f"; }
+.bi-bag-check-fill::before { content: "\f170"; }
+.bi-bag-check::before { content: "\f171"; }
+.bi-bag-dash-fill::before { content: "\f172"; }
+.bi-bag-dash::before { content: "\f173"; }
+.bi-bag-fill::before { content: "\f174"; }
+.bi-bag-plus-fill::before { content: "\f175"; }
+.bi-bag-plus::before { content: "\f176"; }
+.bi-bag-x-fill::before { content: "\f177"; }
+.bi-bag-x::before { content: "\f178"; }
+.bi-bag::before { content: "\f179"; }
+.bi-bar-chart-fill::before { content: "\f17a"; }
+.bi-bar-chart-line-fill::before { content: "\f17b"; }
+.bi-bar-chart-line::before { content: "\f17c"; }
+.bi-bar-chart-steps::before { content: "\f17d"; }
+.bi-bar-chart::before { content: "\f17e"; }
+.bi-basket-fill::before { content: "\f17f"; }
+.bi-basket::before { content: "\f180"; }
+.bi-basket2-fill::before { content: "\f181"; }
+.bi-basket2::before { content: "\f182"; }
+.bi-basket3-fill::before { content: "\f183"; }
+.bi-basket3::before { content: "\f184"; }
+.bi-battery-charging::before { content: "\f185"; }
+.bi-battery-full::before { content: "\f186"; }
+.bi-battery-half::before { content: "\f187"; }
+.bi-battery::before { content: "\f188"; }
+.bi-bell-fill::before { content: "\f189"; }
+.bi-bell::before { content: "\f18a"; }
+.bi-bezier::before { content: "\f18b"; }
+.bi-bezier2::before { content: "\f18c"; }
+.bi-bicycle::before { content: "\f18d"; }
+.bi-binoculars-fill::before { content: "\f18e"; }
+.bi-binoculars::before { content: "\f18f"; }
+.bi-blockquote-left::before { content: "\f190"; }
+.bi-blockquote-right::before { content: "\f191"; }
+.bi-book-fill::before { content: "\f192"; }
+.bi-book-half::before { content: "\f193"; }
+.bi-book::before { content: "\f194"; }
+.bi-bookmark-check-fill::before { content: "\f195"; }
+.bi-bookmark-check::before { content: "\f196"; }
+.bi-bookmark-dash-fill::before { content: "\f197"; }
+.bi-bookmark-dash::before { content: "\f198"; }
+.bi-bookmark-fill::before { content: "\f199"; }
+.bi-bookmark-heart-fill::before { content: "\f19a"; }
+.bi-bookmark-heart::before { content: "\f19b"; }
+.bi-bookmark-plus-fill::before { content: "\f19c"; }
+.bi-bookmark-plus::before { content: "\f19d"; }
+.bi-bookmark-star-fill::before { content: "\f19e"; }
+.bi-bookmark-star::before { content: "\f19f"; }
+.bi-bookmark-x-fill::before { content: "\f1a0"; }
+.bi-bookmark-x::before { content: "\f1a1"; }
+.bi-bookmark::before { content: "\f1a2"; }
+.bi-bookmarks-fill::before { content: "\f1a3"; }
+.bi-bookmarks::before { content: "\f1a4"; }
+.bi-bookshelf::before { content: "\f1a5"; }
+.bi-bootstrap-fill::before { content: "\f1a6"; }
+.bi-bootstrap-reboot::before { content: "\f1a7"; }
+.bi-bootstrap::before { content: "\f1a8"; }
+.bi-border-all::before { content: "\f1a9"; }
+.bi-border-bottom::before { content: "\f1aa"; }
+.bi-border-center::before { content: "\f1ab"; }
+.bi-border-inner::before { content: "\f1ac"; }
+.bi-border-left::before { content: "\f1ad"; }
+.bi-border-middle::before { content: "\f1ae"; }
+.bi-border-outer::before { content: "\f1af"; }
+.bi-border-right::before { content: "\f1b0"; }
+.bi-border-style::before { content: "\f1b1"; }
+.bi-border-top::before { content: "\f1b2"; }
+.bi-border-width::before { content: "\f1b3"; }
+.bi-border::before { content: "\f1b4"; }
+.bi-bounding-box-circles::before { content: "\f1b5"; }
+.bi-bounding-box::before { content: "\f1b6"; }
+.bi-box-arrow-down-left::before { content: "\f1b7"; }
+.bi-box-arrow-down-right::before { content: "\f1b8"; }
+.bi-box-arrow-down::before { content: "\f1b9"; }
+.bi-box-arrow-in-down-left::before { content: "\f1ba"; }
+.bi-box-arrow-in-down-right::before { content: "\f1bb"; }
+.bi-box-arrow-in-down::before { content: "\f1bc"; }
+.bi-box-arrow-in-left::before { content: "\f1bd"; }
+.bi-box-arrow-in-right::before { content: "\f1be"; }
+.bi-box-arrow-in-up-left::before { content: "\f1bf"; }
+.bi-box-arrow-in-up-right::before { content: "\f1c0"; }
+.bi-box-arrow-in-up::before { content: "\f1c1"; }
+.bi-box-arrow-left::before { content: "\f1c2"; }
+.bi-box-arrow-right::before { content: "\f1c3"; }
+.bi-box-arrow-up-left::before { content: "\f1c4"; }
+.bi-box-arrow-up-right::before { content: "\f1c5"; }
+.bi-box-arrow-up::before { content: "\f1c6"; }
+.bi-box-seam::before { content: "\f1c7"; }
+.bi-box::before { content: "\f1c8"; }
+.bi-braces::before { content: "\f1c9"; }
+.bi-bricks::before { content: "\f1ca"; }
+.bi-briefcase-fill::before { content: "\f1cb"; }
+.bi-briefcase::before { content: "\f1cc"; }
+.bi-brightness-alt-high-fill::before { content: "\f1cd"; }
+.bi-brightness-alt-high::before { content: "\f1ce"; }
+.bi-brightness-alt-low-fill::before { content: "\f1cf"; }
+.bi-brightness-alt-low::before { content: "\f1d0"; }
+.bi-brightness-high-fill::before { content: "\f1d1"; }
+.bi-brightness-high::before { content: "\f1d2"; }
+.bi-brightness-low-fill::before { content: "\f1d3"; }
+.bi-brightness-low::before { content: "\f1d4"; }
+.bi-broadcast-pin::before { content: "\f1d5"; }
+.bi-broadcast::before { content: "\f1d6"; }
+.bi-brush-fill::before { content: "\f1d7"; }
+.bi-brush::before { content: "\f1d8"; }
+.bi-bucket-fill::before { content: "\f1d9"; }
+.bi-bucket::before { content: "\f1da"; }
+.bi-bug-fill::before { content: "\f1db"; }
+.bi-bug::before { content: "\f1dc"; }
+.bi-building::before { content: "\f1dd"; }
+.bi-bullseye::before { content: "\f1de"; }
+.bi-calculator-fill::before { content: "\f1df"; }
+.bi-calculator::before { content: "\f1e0"; }
+.bi-calendar-check-fill::before { content: "\f1e1"; }
+.bi-calendar-check::before { content: "\f1e2"; }
+.bi-calendar-date-fill::before { content: "\f1e3"; }
+.bi-calendar-date::before { content: "\f1e4"; }
+.bi-calendar-day-fill::before { content: "\f1e5"; }
+.bi-calendar-day::before { content: "\f1e6"; }
+.bi-calendar-event-fill::before { content: "\f1e7"; }
+.bi-calendar-event::before { content: "\f1e8"; }
+.bi-calendar-fill::before { content: "\f1e9"; }
+.bi-calendar-minus-fill::before { content: "\f1ea"; }
+.bi-calendar-minus::before { content: "\f1eb"; }
+.bi-calendar-month-fill::before { content: "\f1ec"; }
+.bi-calendar-month::before { content: "\f1ed"; }
+.bi-calendar-plus-fill::before { content: "\f1ee"; }
+.bi-calendar-plus::before { content: "\f1ef"; }
+.bi-calendar-range-fill::before { content: "\f1f0"; }
+.bi-calendar-range::before { content: "\f1f1"; }
+.bi-calendar-week-fill::before { content: "\f1f2"; }
+.bi-calendar-week::before { content: "\f1f3"; }
+.bi-calendar-x-fill::before { content: "\f1f4"; }
+.bi-calendar-x::before { content: "\f1f5"; }
+.bi-calendar::before { content: "\f1f6"; }
+.bi-calendar2-check-fill::before { content: "\f1f7"; }
+.bi-calendar2-check::before { content: "\f1f8"; }
+.bi-calendar2-date-fill::before { content: "\f1f9"; }
+.bi-calendar2-date::before { content: "\f1fa"; }
+.bi-calendar2-day-fill::before { content: "\f1fb"; }
+.bi-calendar2-day::before { content: "\f1fc"; }
+.bi-calendar2-event-fill::before { content: "\f1fd"; }
+.bi-calendar2-event::before { content: "\f1fe"; }
+.bi-calendar2-fill::before { content: "\f1ff"; }
+.bi-calendar2-minus-fill::before { content: "\f200"; }
+.bi-calendar2-minus::before { content: "\f201"; }
+.bi-calendar2-month-fill::before { content: "\f202"; }
+.bi-calendar2-month::before { content: "\f203"; }
+.bi-calendar2-plus-fill::before { content: "\f204"; }
+.bi-calendar2-plus::before { content: "\f205"; }
+.bi-calendar2-range-fill::before { content: "\f206"; }
+.bi-calendar2-range::before { content: "\f207"; }
+.bi-calendar2-week-fill::before { content: "\f208"; }
+.bi-calendar2-week::before { content: "\f209"; }
+.bi-calendar2-x-fill::before { content: "\f20a"; }
+.bi-calendar2-x::before { content: "\f20b"; }
+.bi-calendar2::before { content: "\f20c"; }
+.bi-calendar3-event-fill::before { content: "\f20d"; }
+.bi-calendar3-event::before { content: "\f20e"; }
+.bi-calendar3-fill::before { content: "\f20f"; }
+.bi-calendar3-range-fill::before { content: "\f210"; }
+.bi-calendar3-range::before { content: "\f211"; }
+.bi-calendar3-week-fill::before { content: "\f212"; }
+.bi-calendar3-week::before { content: "\f213"; }
+.bi-calendar3::before { content: "\f214"; }
+.bi-calendar4-event::before { content: "\f215"; }
+.bi-calendar4-range::before { content: "\f216"; }
+.bi-calendar4-week::before { content: "\f217"; }
+.bi-calendar4::before { content: "\f218"; }
+.bi-camera-fill::before { content: "\f219"; }
+.bi-camera-reels-fill::before { content: "\f21a"; }
+.bi-camera-reels::before { content: "\f21b"; }
+.bi-camera-video-fill::before { content: "\f21c"; }
+.bi-camera-video-off-fill::before { content: "\f21d"; }
+.bi-camera-video-off::before { content: "\f21e"; }
+.bi-camera-video::before { content: "\f21f"; }
+.bi-camera::before { content: "\f220"; }
+.bi-camera2::before { content: "\f221"; }
+.bi-capslock-fill::before { content: "\f222"; }
+.bi-capslock::before { content: "\f223"; }
+.bi-card-checklist::before { content: "\f224"; }
+.bi-card-heading::before { content: "\f225"; }
+.bi-card-image::before { content: "\f226"; }
+.bi-card-list::before { content: "\f227"; }
+.bi-card-text::before { content: "\f228"; }
+.bi-caret-down-fill::before { content: "\f229"; }
+.bi-caret-down-square-fill::before { content: "\f22a"; }
+.bi-caret-down-square::before { content: "\f22b"; }
+.bi-caret-down::before { content: "\f22c"; }
+.bi-caret-left-fill::before { content: "\f22d"; }
+.bi-caret-left-square-fill::before { content: "\f22e"; }
+.bi-caret-left-square::before { content: "\f22f"; }
+.bi-caret-left::before { content: "\f230"; }
+.bi-caret-right-fill::before { content: "\f231"; }
+.bi-caret-right-square-fill::before { content: "\f232"; }
+.bi-caret-right-square::before { content: "\f233"; }
+.bi-caret-right::before { content: "\f234"; }
+.bi-caret-up-fill::before { content: "\f235"; }
+.bi-caret-up-square-fill::before { content: "\f236"; }
+.bi-caret-up-square::before { content: "\f237"; }
+.bi-caret-up::before { content: "\f238"; }
+.bi-cart-check-fill::before { content: "\f239"; }
+.bi-cart-check::before { content: "\f23a"; }
+.bi-cart-dash-fill::before { content: "\f23b"; }
+.bi-cart-dash::before { content: "\f23c"; }
+.bi-cart-fill::before { content: "\f23d"; }
+.bi-cart-plus-fill::before { content: "\f23e"; }
+.bi-cart-plus::before { content: "\f23f"; }
+.bi-cart-x-fill::before { content: "\f240"; }
+.bi-cart-x::before { content: "\f241"; }
+.bi-cart::before { content: "\f242"; }
+.bi-cart2::before { content: "\f243"; }
+.bi-cart3::before { content: "\f244"; }
+.bi-cart4::before { content: "\f245"; }
+.bi-cash-stack::before { content: "\f246"; }
+.bi-cash::before { content: "\f247"; }
+.bi-cast::before { content: "\f248"; }
+.bi-chat-dots-fill::before { content: "\f249"; }
+.bi-chat-dots::before { content: "\f24a"; }
+.bi-chat-fill::before { content: "\f24b"; }
+.bi-chat-left-dots-fill::before { content: "\f24c"; }
+.bi-chat-left-dots::before { content: "\f24d"; }
+.bi-chat-left-fill::before { content: "\f24e"; }
+.bi-chat-left-quote-fill::before { content: "\f24f"; }
+.bi-chat-left-quote::before { content: "\f250"; }
+.bi-chat-left-text-fill::before { content: "\f251"; }
+.bi-chat-left-text::before { content: "\f252"; }
+.bi-chat-left::before { content: "\f253"; }
+.bi-chat-quote-fill::before { content: "\f254"; }
+.bi-chat-quote::before { content: "\f255"; }
+.bi-chat-right-dots-fill::before { content: "\f256"; }
+.bi-chat-right-dots::before { content: "\f257"; }
+.bi-chat-right-fill::before { content: "\f258"; }
+.bi-chat-right-quote-fill::before { content: "\f259"; }
+.bi-chat-right-quote::before { content: "\f25a"; }
+.bi-chat-right-text-fill::before { content: "\f25b"; }
+.bi-chat-right-text::before { content: "\f25c"; }
+.bi-chat-right::before { content: "\f25d"; }
+.bi-chat-square-dots-fill::before { content: "\f25e"; }
+.bi-chat-square-dots::before { content: "\f25f"; }
+.bi-chat-square-fill::before { content: "\f260"; }
+.bi-chat-square-quote-fill::before { content: "\f261"; }
+.bi-chat-square-quote::before { content: "\f262"; }
+.bi-chat-square-text-fill::before { content: "\f263"; }
+.bi-chat-square-text::before { content: "\f264"; }
+.bi-chat-square::before { content: "\f265"; }
+.bi-chat-text-fill::before { content: "\f266"; }
+.bi-chat-text::before { content: "\f267"; }
+.bi-chat::before { content: "\f268"; }
+.bi-check-all::before { content: "\f269"; }
+.bi-check-circle-fill::before { content: "\f26a"; }
+.bi-check-circle::before { content: "\f26b"; }
+.bi-check-square-fill::before { content: "\f26c"; }
+.bi-check-square::before { content: "\f26d"; }
+.bi-check::before { content: "\f26e"; }
+.bi-check2-all::before { content: "\f26f"; }
+.bi-check2-circle::before { content: "\f270"; }
+.bi-check2-square::before { content: "\f271"; }
+.bi-check2::before { content: "\f272"; }
+.bi-chevron-bar-contract::before { content: "\f273"; }
+.bi-chevron-bar-down::before { content: "\f274"; }
+.bi-chevron-bar-expand::before { content: "\f275"; }
+.bi-chevron-bar-left::before { content: "\f276"; }
+.bi-chevron-bar-right::before { content: "\f277"; }
+.bi-chevron-bar-up::before { content: "\f278"; }
+.bi-chevron-compact-down::before { content: "\f279"; }
+.bi-chevron-compact-left::before { content: "\f27a"; }
+.bi-chevron-compact-right::before { content: "\f27b"; }
+.bi-chevron-compact-up::before { content: "\f27c"; }
+.bi-chevron-contract::before { content: "\f27d"; }
+.bi-chevron-double-down::before { content: "\f27e"; }
+.bi-chevron-double-left::before { content: "\f27f"; }
+.bi-chevron-double-right::before { content: "\f280"; }
+.bi-chevron-double-up::before { content: "\f281"; }
+.bi-chevron-down::before { content: "\f282"; }
+.bi-chevron-expand::before { content: "\f283"; }
+.bi-chevron-left::before { content: "\f284"; }
+.bi-chevron-right::before { content: "\f285"; }
+.bi-chevron-up::before { content: "\f286"; }
+.bi-circle-fill::before { content: "\f287"; }
+.bi-circle-half::before { content: "\f288"; }
+.bi-circle-square::before { content: "\f289"; }
+.bi-circle::before { content: "\f28a"; }
+.bi-clipboard-check::before { content: "\f28b"; }
+.bi-clipboard-data::before { content: "\f28c"; }
+.bi-clipboard-minus::before { content: "\f28d"; }
+.bi-clipboard-plus::before { content: "\f28e"; }
+.bi-clipboard-x::before { content: "\f28f"; }
+.bi-clipboard::before { content: "\f290"; }
+.bi-clock-fill::before { content: "\f291"; }
+.bi-clock-history::before { content: "\f292"; }
+.bi-clock::before { content: "\f293"; }
+.bi-cloud-arrow-down-fill::before { content: "\f294"; }
+.bi-cloud-arrow-down::before { content: "\f295"; }
+.bi-cloud-arrow-up-fill::before { content: "\f296"; }
+.bi-cloud-arrow-up::before { content: "\f297"; }
+.bi-cloud-check-fill::before { content: "\f298"; }
+.bi-cloud-check::before { content: "\f299"; }
+.bi-cloud-download-fill::before { content: "\f29a"; }
+.bi-cloud-download::before { content: "\f29b"; }
+.bi-cloud-drizzle-fill::before { content: "\f29c"; }
+.bi-cloud-drizzle::before { content: "\f29d"; }
+.bi-cloud-fill::before { content: "\f29e"; }
+.bi-cloud-fog-fill::before { content: "\f29f"; }
+.bi-cloud-fog::before { content: "\f2a0"; }
+.bi-cloud-fog2-fill::before { content: "\f2a1"; }
+.bi-cloud-fog2::before { content: "\f2a2"; }
+.bi-cloud-hail-fill::before { content: "\f2a3"; }
+.bi-cloud-hail::before { content: "\f2a4"; }
+.bi-cloud-haze-1::before { content: "\f2a5"; }
+.bi-cloud-haze-fill::before { content: "\f2a6"; }
+.bi-cloud-haze::before { content: "\f2a7"; }
+.bi-cloud-haze2-fill::before { content: "\f2a8"; }
+.bi-cloud-lightning-fill::before { content: "\f2a9"; }
+.bi-cloud-lightning-rain-fill::before { content: "\f2aa"; }
+.bi-cloud-lightning-rain::before { content: "\f2ab"; }
+.bi-cloud-lightning::before { content: "\f2ac"; }
+.bi-cloud-minus-fill::before { content: "\f2ad"; }
+.bi-cloud-minus::before { content: "\f2ae"; }
+.bi-cloud-moon-fill::before { content: "\f2af"; }
+.bi-cloud-moon::before { content: "\f2b0"; }
+.bi-cloud-plus-fill::before { content: "\f2b1"; }
+.bi-cloud-plus::before { content: "\f2b2"; }
+.bi-cloud-rain-fill::before { content: "\f2b3"; }
+.bi-cloud-rain-heavy-fill::before { content: "\f2b4"; }
+.bi-cloud-rain-heavy::before { content: "\f2b5"; }
+.bi-cloud-rain::before { content: "\f2b6"; }
+.bi-cloud-slash-fill::before { content: "\f2b7"; }
+.bi-cloud-slash::before { content: "\f2b8"; }
+.bi-cloud-sleet-fill::before { content: "\f2b9"; }
+.bi-cloud-sleet::before { content: "\f2ba"; }
+.bi-cloud-snow-fill::before { content: "\f2bb"; }
+.bi-cloud-snow::before { content: "\f2bc"; }
+.bi-cloud-sun-fill::before { content: "\f2bd"; }
+.bi-cloud-sun::before { content: "\f2be"; }
+.bi-cloud-upload-fill::before { content: "\f2bf"; }
+.bi-cloud-upload::before { content: "\f2c0"; }
+.bi-cloud::before { content: "\f2c1"; }
+.bi-clouds-fill::before { content: "\f2c2"; }
+.bi-clouds::before { content: "\f2c3"; }
+.bi-cloudy-fill::before { content: "\f2c4"; }
+.bi-cloudy::before { content: "\f2c5"; }
+.bi-code-slash::before { content: "\f2c6"; }
+.bi-code-square::before { content: "\f2c7"; }
+.bi-code::before { content: "\f2c8"; }
+.bi-collection-fill::before { content: "\f2c9"; }
+.bi-collection-play-fill::before { content: "\f2ca"; }
+.bi-collection-play::before { content: "\f2cb"; }
+.bi-collection::before { content: "\f2cc"; }
+.bi-columns-gap::before { content: "\f2cd"; }
+.bi-columns::before { content: "\f2ce"; }
+.bi-command::before { content: "\f2cf"; }
+.bi-compass-fill::before { content: "\f2d0"; }
+.bi-compass::before { content: "\f2d1"; }
+.bi-cone-striped::before { content: "\f2d2"; }
+.bi-cone::before { content: "\f2d3"; }
+.bi-controller::before { content: "\f2d4"; }
+.bi-cpu-fill::before { content: "\f2d5"; }
+.bi-cpu::before { content: "\f2d6"; }
+.bi-credit-card-2-back-fill::before { content: "\f2d7"; }
+.bi-credit-card-2-back::before { content: "\f2d8"; }
+.bi-credit-card-2-front-fill::before { content: "\f2d9"; }
+.bi-credit-card-2-front::before { content: "\f2da"; }
+.bi-credit-card-fill::before { content: "\f2db"; }
+.bi-credit-card::before { content: "\f2dc"; }
+.bi-crop::before { content: "\f2dd"; }
+.bi-cup-fill::before { content: "\f2de"; }
+.bi-cup-straw::before { content: "\f2df"; }
+.bi-cup::before { content: "\f2e0"; }
+.bi-cursor-fill::before { content: "\f2e1"; }
+.bi-cursor-text::before { content: "\f2e2"; }
+.bi-cursor::before { content: "\f2e3"; }
+.bi-dash-circle-dotted::before { content: "\f2e4"; }
+.bi-dash-circle-fill::before { content: "\f2e5"; }
+.bi-dash-circle::before { content: "\f2e6"; }
+.bi-dash-square-dotted::before { content: "\f2e7"; }
+.bi-dash-square-fill::before { content: "\f2e8"; }
+.bi-dash-square::before { content: "\f2e9"; }
+.bi-dash::before { content: "\f2ea"; }
+.bi-diagram-2-fill::before { content: "\f2eb"; }
+.bi-diagram-2::before { content: "\f2ec"; }
+.bi-diagram-3-fill::before { content: "\f2ed"; }
+.bi-diagram-3::before { content: "\f2ee"; }
+.bi-diamond-fill::before { content: "\f2ef"; }
+.bi-diamond-half::before { content: "\f2f0"; }
+.bi-diamond::before { content: "\f2f1"; }
+.bi-dice-1-fill::before { content: "\f2f2"; }
+.bi-dice-1::before { content: "\f2f3"; }
+.bi-dice-2-fill::before { content: "\f2f4"; }
+.bi-dice-2::before { content: "\f2f5"; }
+.bi-dice-3-fill::before { content: "\f2f6"; }
+.bi-dice-3::before { content: "\f2f7"; }
+.bi-dice-4-fill::before { content: "\f2f8"; }
+.bi-dice-4::before { content: "\f2f9"; }
+.bi-dice-5-fill::before { content: "\f2fa"; }
+.bi-dice-5::before { content: "\f2fb"; }
+.bi-dice-6-fill::before { content: "\f2fc"; }
+.bi-dice-6::before { content: "\f2fd"; }
+.bi-disc-fill::before { content: "\f2fe"; }
+.bi-disc::before { content: "\f2ff"; }
+.bi-discord::before { content: "\f300"; }
+.bi-display-fill::before { content: "\f301"; }
+.bi-display::before { content: "\f302"; }
+.bi-distribute-horizontal::before { content: "\f303"; }
+.bi-distribute-vertical::before { content: "\f304"; }
+.bi-door-closed-fill::before { content: "\f305"; }
+.bi-door-closed::before { content: "\f306"; }
+.bi-door-open-fill::before { content: "\f307"; }
+.bi-door-open::before { content: "\f308"; }
+.bi-dot::before { content: "\f309"; }
+.bi-download::before { content: "\f30a"; }
+.bi-droplet-fill::before { content: "\f30b"; }
+.bi-droplet-half::before { content: "\f30c"; }
+.bi-droplet::before { content: "\f30d"; }
+.bi-earbuds::before { content: "\f30e"; }
+.bi-easel-fill::before { content: "\f30f"; }
+.bi-easel::before { content: "\f310"; }
+.bi-egg-fill::before { content: "\f311"; }
+.bi-egg-fried::before { content: "\f312"; }
+.bi-egg::before { content: "\f313"; }
+.bi-eject-fill::before { content: "\f314"; }
+.bi-eject::before { content: "\f315"; }
+.bi-emoji-angry-fill::before { content: "\f316"; }
+.bi-emoji-angry::before { content: "\f317"; }
+.bi-emoji-dizzy-fill::before { content: "\f318"; }
+.bi-emoji-dizzy::before { content: "\f319"; }
+.bi-emoji-expressionless-fill::before { content: "\f31a"; }
+.bi-emoji-expressionless::before { content: "\f31b"; }
+.bi-emoji-frown-fill::before { content: "\f31c"; }
+.bi-emoji-frown::before { content: "\f31d"; }
+.bi-emoji-heart-eyes-fill::before { content: "\f31e"; }
+.bi-emoji-heart-eyes::before { content: "\f31f"; }
+.bi-emoji-laughing-fill::before { content: "\f320"; }
+.bi-emoji-laughing::before { content: "\f321"; }
+.bi-emoji-neutral-fill::before { content: "\f322"; }
+.bi-emoji-neutral::before { content: "\f323"; }
+.bi-emoji-smile-fill::before { content: "\f324"; }
+.bi-emoji-smile-upside-down-fill::before { content: "\f325"; }
+.bi-emoji-smile-upside-down::before { content: "\f326"; }
+.bi-emoji-smile::before { content: "\f327"; }
+.bi-emoji-sunglasses-fill::before { content: "\f328"; }
+.bi-emoji-sunglasses::before { content: "\f329"; }
+.bi-emoji-wink-fill::before { content: "\f32a"; }
+.bi-emoji-wink::before { content: "\f32b"; }
+.bi-envelope-fill::before { content: "\f32c"; }
+.bi-envelope-open-fill::before { content: "\f32d"; }
+.bi-envelope-open::before { content: "\f32e"; }
+.bi-envelope::before { content: "\f32f"; }
+.bi-eraser-fill::before { content: "\f330"; }
+.bi-eraser::before { content: "\f331"; }
+.bi-exclamation-circle-fill::before { content: "\f332"; }
+.bi-exclamation-circle::before { content: "\f333"; }
+.bi-exclamation-diamond-fill::before { content: "\f334"; }
+.bi-exclamation-diamond::before { content: "\f335"; }
+.bi-exclamation-octagon-fill::before { content: "\f336"; }
+.bi-exclamation-octagon::before { content: "\f337"; }
+.bi-exclamation-square-fill::before { content: "\f338"; }
+.bi-exclamation-square::before { content: "\f339"; }
+.bi-exclamation-triangle-fill::before { content: "\f33a"; }
+.bi-exclamation-triangle::before { content: "\f33b"; }
+.bi-exclamation::before { content: "\f33c"; }
+.bi-exclude::before { content: "\f33d"; }
+.bi-eye-fill::before { content: "\f33e"; }
+.bi-eye-slash-fill::before { content: "\f33f"; }
+.bi-eye-slash::before { content: "\f340"; }
+.bi-eye::before { content: "\f341"; }
+.bi-eyedropper::before { content: "\f342"; }
+.bi-eyeglasses::before { content: "\f343"; }
+.bi-facebook::before { content: "\f344"; }
+.bi-file-arrow-down-fill::before { content: "\f345"; }
+.bi-file-arrow-down::before { content: "\f346"; }
+.bi-file-arrow-up-fill::before { content: "\f347"; }
+.bi-file-arrow-up::before { content: "\f348"; }
+.bi-file-bar-graph-fill::before { content: "\f349"; }
+.bi-file-bar-graph::before { content: "\f34a"; }
+.bi-file-binary-fill::before { content: "\f34b"; }
+.bi-file-binary::before { content: "\f34c"; }
+.bi-file-break-fill::before { content: "\f34d"; }
+.bi-file-break::before { content: "\f34e"; }
+.bi-file-check-fill::before { content: "\f34f"; }
+.bi-file-check::before { content: "\f350"; }
+.bi-file-code-fill::before { content: "\f351"; }
+.bi-file-code::before { content: "\f352"; }
+.bi-file-diff-fill::before { content: "\f353"; }
+.bi-file-diff::before { content: "\f354"; }
+.bi-file-earmark-arrow-down-fill::before { content: "\f355"; }
+.bi-file-earmark-arrow-down::before { content: "\f356"; }
+.bi-file-earmark-arrow-up-fill::before { content: "\f357"; }
+.bi-file-earmark-arrow-up::before { content: "\f358"; }
+.bi-file-earmark-bar-graph-fill::before { content: "\f359"; }
+.bi-file-earmark-bar-graph::before { content: "\f35a"; }
+.bi-file-earmark-binary-fill::before { content: "\f35b"; }
+.bi-file-earmark-binary::before { content: "\f35c"; }
+.bi-file-earmark-break-fill::before { content: "\f35d"; }
+.bi-file-earmark-break::before { content: "\f35e"; }
+.bi-file-earmark-check-fill::before { content: "\f35f"; }
+.bi-file-earmark-check::before { content: "\f360"; }
+.bi-file-earmark-code-fill::before { content: "\f361"; }
+.bi-file-earmark-code::before { content: "\f362"; }
+.bi-file-earmark-diff-fill::before { content: "\f363"; }
+.bi-file-earmark-diff::before { content: "\f364"; }
+.bi-file-earmark-easel-fill::before { content: "\f365"; }
+.bi-file-earmark-easel::before { content: "\f366"; }
+.bi-file-earmark-excel-fill::before { content: "\f367"; }
+.bi-file-earmark-excel::before { content: "\f368"; }
+.bi-file-earmark-fill::before { content: "\f369"; }
+.bi-file-earmark-font-fill::before { content: "\f36a"; }
+.bi-file-earmark-font::before { content: "\f36b"; }
+.bi-file-earmark-image-fill::before { content: "\f36c"; }
+.bi-file-earmark-image::before { content: "\f36d"; }
+.bi-file-earmark-lock-fill::before { content: "\f36e"; }
+.bi-file-earmark-lock::before { content: "\f36f"; }
+.bi-file-earmark-lock2-fill::before { content: "\f370"; }
+.bi-file-earmark-lock2::before { content: "\f371"; }
+.bi-file-earmark-medical-fill::before { content: "\f372"; }
+.bi-file-earmark-medical::before { content: "\f373"; }
+.bi-file-earmark-minus-fill::before { content: "\f374"; }
+.bi-file-earmark-minus::before { content: "\f375"; }
+.bi-file-earmark-music-fill::before { content: "\f376"; }
+.bi-file-earmark-music::before { content: "\f377"; }
+.bi-file-earmark-person-fill::before { content: "\f378"; }
+.bi-file-earmark-person::before { content: "\f379"; }
+.bi-file-earmark-play-fill::before { content: "\f37a"; }
+.bi-file-earmark-play::before { content: "\f37b"; }
+.bi-file-earmark-plus-fill::before { content: "\f37c"; }
+.bi-file-earmark-plus::before { content: "\f37d"; }
+.bi-file-earmark-post-fill::before { content: "\f37e"; }
+.bi-file-earmark-post::before { content: "\f37f"; }
+.bi-file-earmark-ppt-fill::before { content: "\f380"; }
+.bi-file-earmark-ppt::before { content: "\f381"; }
+.bi-file-earmark-richtext-fill::before { content: "\f382"; }
+.bi-file-earmark-richtext::before { content: "\f383"; }
+.bi-file-earmark-ruled-fill::before { content: "\f384"; }
+.bi-file-earmark-ruled::before { content: "\f385"; }
+.bi-file-earmark-slides-fill::before { content: "\f386"; }
+.bi-file-earmark-slides::before { content: "\f387"; }
+.bi-file-earmark-spreadsheet-fill::before { content: "\f388"; }
+.bi-file-earmark-spreadsheet::before { content: "\f389"; }
+.bi-file-earmark-text-fill::before { content: "\f38a"; }
+.bi-file-earmark-text::before { content: "\f38b"; }
+.bi-file-earmark-word-fill::before { content: "\f38c"; }
+.bi-file-earmark-word::before { content: "\f38d"; }
+.bi-file-earmark-x-fill::before { content: "\f38e"; }
+.bi-file-earmark-x::before { content: "\f38f"; }
+.bi-file-earmark-zip-fill::before { content: "\f390"; }
+.bi-file-earmark-zip::before { content: "\f391"; }
+.bi-file-earmark::before { content: "\f392"; }
+.bi-file-easel-fill::before { content: "\f393"; }
+.bi-file-easel::before { content: "\f394"; }
+.bi-file-excel-fill::before { content: "\f395"; }
+.bi-file-excel::before { content: "\f396"; }
+.bi-file-fill::before { content: "\f397"; }
+.bi-file-font-fill::before { content: "\f398"; }
+.bi-file-font::before { content: "\f399"; }
+.bi-file-image-fill::before { content: "\f39a"; }
+.bi-file-image::before { content: "\f39b"; }
+.bi-file-lock-fill::before { content: "\f39c"; }
+.bi-file-lock::before { content: "\f39d"; }
+.bi-file-lock2-fill::before { content: "\f39e"; }
+.bi-file-lock2::before { content: "\f39f"; }
+.bi-file-medical-fill::before { content: "\f3a0"; }
+.bi-file-medical::before { content: "\f3a1"; }
+.bi-file-minus-fill::before { content: "\f3a2"; }
+.bi-file-minus::before { content: "\f3a3"; }
+.bi-file-music-fill::before { content: "\f3a4"; }
+.bi-file-music::before { content: "\f3a5"; }
+.bi-file-person-fill::before { content: "\f3a6"; }
+.bi-file-person::before { content: "\f3a7"; }
+.bi-file-play-fill::before { content: "\f3a8"; }
+.bi-file-play::before { content: "\f3a9"; }
+.bi-file-plus-fill::before { content: "\f3aa"; }
+.bi-file-plus::before { content: "\f3ab"; }
+.bi-file-post-fill::before { content: "\f3ac"; }
+.bi-file-post::before { content: "\f3ad"; }
+.bi-file-ppt-fill::before { content: "\f3ae"; }
+.bi-file-ppt::before { content: "\f3af"; }
+.bi-file-richtext-fill::before { content: "\f3b0"; }
+.bi-file-richtext::before { content: "\f3b1"; }
+.bi-file-ruled-fill::before { content: "\f3b2"; }
+.bi-file-ruled::before { content: "\f3b3"; }
+.bi-file-slides-fill::before { content: "\f3b4"; }
+.bi-file-slides::before { content: "\f3b5"; }
+.bi-file-spreadsheet-fill::before { content: "\f3b6"; }
+.bi-file-spreadsheet::before { content: "\f3b7"; }
+.bi-file-text-fill::before { content: "\f3b8"; }
+.bi-file-text::before { content: "\f3b9"; }
+.bi-file-word-fill::before { content: "\f3ba"; }
+.bi-file-word::before { content: "\f3bb"; }
+.bi-file-x-fill::before { content: "\f3bc"; }
+.bi-file-x::before { content: "\f3bd"; }
+.bi-file-zip-fill::before { content: "\f3be"; }
+.bi-file-zip::before { content: "\f3bf"; }
+.bi-file::before { content: "\f3c0"; }
+.bi-files-alt::before { content: "\f3c1"; }
+.bi-files::before { content: "\f3c2"; }
+.bi-film::before { content: "\f3c3"; }
+.bi-filter-circle-fill::before { content: "\f3c4"; }
+.bi-filter-circle::before { content: "\f3c5"; }
+.bi-filter-left::before { content: "\f3c6"; }
+.bi-filter-right::before { content: "\f3c7"; }
+.bi-filter-square-fill::before { content: "\f3c8"; }
+.bi-filter-square::before { content: "\f3c9"; }
+.bi-filter::before { content: "\f3ca"; }
+.bi-flag-fill::before { content: "\f3cb"; }
+.bi-flag::before { content: "\f3cc"; }
+.bi-flower1::before { content: "\f3cd"; }
+.bi-flower2::before { content: "\f3ce"; }
+.bi-flower3::before { content: "\f3cf"; }
+.bi-folder-check::before { content: "\f3d0"; }
+.bi-folder-fill::before { content: "\f3d1"; }
+.bi-folder-minus::before { content: "\f3d2"; }
+.bi-folder-plus::before { content: "\f3d3"; }
+.bi-folder-symlink-fill::before { content: "\f3d4"; }
+.bi-folder-symlink::before { content: "\f3d5"; }
+.bi-folder-x::before { content: "\f3d6"; }
+.bi-folder::before { content: "\f3d7"; }
+.bi-folder2-open::before { content: "\f3d8"; }
+.bi-folder2::before { content: "\f3d9"; }
+.bi-fonts::before { content: "\f3da"; }
+.bi-forward-fill::before { content: "\f3db"; }
+.bi-forward::before { content: "\f3dc"; }
+.bi-front::before { content: "\f3dd"; }
+.bi-fullscreen-exit::before { content: "\f3de"; }
+.bi-fullscreen::before { content: "\f3df"; }
+.bi-funnel-fill::before { content: "\f3e0"; }
+.bi-funnel::before { content: "\f3e1"; }
+.bi-gear-fill::before { content: "\f3e2"; }
+.bi-gear-wide-connected::before { content: "\f3e3"; }
+.bi-gear-wide::before { content: "\f3e4"; }
+.bi-gear::before { content: "\f3e5"; }
+.bi-gem::before { content: "\f3e6"; }
+.bi-geo-alt-fill::before { content: "\f3e7"; }
+.bi-geo-alt::before { content: "\f3e8"; }
+.bi-geo-fill::before { content: "\f3e9"; }
+.bi-geo::before { content: "\f3ea"; }
+.bi-gift-fill::before { content: "\f3eb"; }
+.bi-gift::before { content: "\f3ec"; }
+.bi-github::before { content: "\f3ed"; }
+.bi-globe::before { content: "\f3ee"; }
+.bi-globe2::before { content: "\f3ef"; }
+.bi-google::before { content: "\f3f0"; }
+.bi-graph-down::before { content: "\f3f1"; }
+.bi-graph-up::before { content: "\f3f2"; }
+.bi-grid-1x2-fill::before { content: "\f3f3"; }
+.bi-grid-1x2::before { content: "\f3f4"; }
+.bi-grid-3x2-gap-fill::before { content: "\f3f5"; }
+.bi-grid-3x2-gap::before { content: "\f3f6"; }
+.bi-grid-3x2::before { content: "\f3f7"; }
+.bi-grid-3x3-gap-fill::before { content: "\f3f8"; }
+.bi-grid-3x3-gap::before { content: "\f3f9"; }
+.bi-grid-3x3::before { content: "\f3fa"; }
+.bi-grid-fill::before { content: "\f3fb"; }
+.bi-grid::before { content: "\f3fc"; }
+.bi-grip-horizontal::before { content: "\f3fd"; }
+.bi-grip-vertical::before { content: "\f3fe"; }
+.bi-hammer::before { content: "\f3ff"; }
+.bi-hand-index-fill::before { content: "\f400"; }
+.bi-hand-index-thumb-fill::before { content: "\f401"; }
+.bi-hand-index-thumb::before { content: "\f402"; }
+.bi-hand-index::before { content: "\f403"; }
+.bi-hand-thumbs-down-fill::before { content: "\f404"; }
+.bi-hand-thumbs-down::before { content: "\f405"; }
+.bi-hand-thumbs-up-fill::before { content: "\f406"; }
+.bi-hand-thumbs-up::before { content: "\f407"; }
+.bi-handbag-fill::before { content: "\f408"; }
+.bi-handbag::before { content: "\f409"; }
+.bi-hash::before { content: "\f40a"; }
+.bi-hdd-fill::before { content: "\f40b"; }
+.bi-hdd-network-fill::before { content: "\f40c"; }
+.bi-hdd-network::before { content: "\f40d"; }
+.bi-hdd-rack-fill::before { content: "\f40e"; }
+.bi-hdd-rack::before { content: "\f40f"; }
+.bi-hdd-stack-fill::before { content: "\f410"; }
+.bi-hdd-stack::before { content: "\f411"; }
+.bi-hdd::before { content: "\f412"; }
+.bi-headphones::before { content: "\f413"; }
+.bi-headset::before { content: "\f414"; }
+.bi-heart-fill::before { content: "\f415"; }
+.bi-heart-half::before { content: "\f416"; }
+.bi-heart::before { content: "\f417"; }
+.bi-heptagon-fill::before { content: "\f418"; }
+.bi-heptagon-half::before { content: "\f419"; }
+.bi-heptagon::before { content: "\f41a"; }
+.bi-hexagon-fill::before { content: "\f41b"; }
+.bi-hexagon-half::before { content: "\f41c"; }
+.bi-hexagon::before { content: "\f41d"; }
+.bi-hourglass-bottom::before { content: "\f41e"; }
+.bi-hourglass-split::before { content: "\f41f"; }
+.bi-hourglass-top::before { content: "\f420"; }
+.bi-hourglass::before { content: "\f421"; }
+.bi-house-door-fill::before { content: "\f422"; }
+.bi-house-door::before { content: "\f423"; }
+.bi-house-fill::before { content: "\f424"; }
+.bi-house::before { content: "\f425"; }
+.bi-hr::before { content: "\f426"; }
+.bi-hurricane::before { content: "\f427"; }
+.bi-image-alt::before { content: "\f428"; }
+.bi-image-fill::before { content: "\f429"; }
+.bi-image::before { content: "\f42a"; }
+.bi-images::before { content: "\f42b"; }
+.bi-inbox-fill::before { content: "\f42c"; }
+.bi-inbox::before { content: "\f42d"; }
+.bi-inboxes-fill::before { content: "\f42e"; }
+.bi-inboxes::before { content: "\f42f"; }
+.bi-info-circle-fill::before { content: "\f430"; }
+.bi-info-circle::before { content: "\f431"; }
+.bi-info-square-fill::before { content: "\f432"; }
+.bi-info-square::before { content: "\f433"; }
+.bi-info::before { content: "\f434"; }
+.bi-input-cursor-text::before { content: "\f435"; }
+.bi-input-cursor::before { content: "\f436"; }
+.bi-instagram::before { content: "\f437"; }
+.bi-intersect::before { content: "\f438"; }
+.bi-journal-album::before { content: "\f439"; }
+.bi-journal-arrow-down::before { content: "\f43a"; }
+.bi-journal-arrow-up::before { content: "\f43b"; }
+.bi-journal-bookmark-fill::before { content: "\f43c"; }
+.bi-journal-bookmark::before { content: "\f43d"; }
+.bi-journal-check::before { content: "\f43e"; }
+.bi-journal-code::before { content: "\f43f"; }
+.bi-journal-medical::before { content: "\f440"; }
+.bi-journal-minus::before { content: "\f441"; }
+.bi-journal-plus::before { content: "\f442"; }
+.bi-journal-richtext::before { content: "\f443"; }
+.bi-journal-text::before { content: "\f444"; }
+.bi-journal-x::before { content: "\f445"; }
+.bi-journal::before { content: "\f446"; }
+.bi-journals::before { content: "\f447"; }
+.bi-joystick::before { content: "\f448"; }
+.bi-justify-left::before { content: "\f449"; }
+.bi-justify-right::before { content: "\f44a"; }
+.bi-justify::before { content: "\f44b"; }
+.bi-kanban-fill::before { content: "\f44c"; }
+.bi-kanban::before { content: "\f44d"; }
+.bi-key-fill::before { content: "\f44e"; }
+.bi-key::before { content: "\f44f"; }
+.bi-keyboard-fill::before { content: "\f450"; }
+.bi-keyboard::before { content: "\f451"; }
+.bi-ladder::before { content: "\f452"; }
+.bi-lamp-fill::before { content: "\f453"; }
+.bi-lamp::before { content: "\f454"; }
+.bi-laptop-fill::before { content: "\f455"; }
+.bi-laptop::before { content: "\f456"; }
+.bi-layer-backward::before { content: "\f457"; }
+.bi-layer-forward::before { content: "\f458"; }
+.bi-layers-fill::before { content: "\f459"; }
+.bi-layers-half::before { content: "\f45a"; }
+.bi-layers::before { content: "\f45b"; }
+.bi-layout-sidebar-inset-reverse::before { content: "\f45c"; }
+.bi-layout-sidebar-inset::before { content: "\f45d"; }
+.bi-layout-sidebar-reverse::before { content: "\f45e"; }
+.bi-layout-sidebar::before { content: "\f45f"; }
+.bi-layout-split::before { content: "\f460"; }
+.bi-layout-text-sidebar-reverse::before { content: "\f461"; }
+.bi-layout-text-sidebar::before { content: "\f462"; }
+.bi-layout-text-window-reverse::before { content: "\f463"; }
+.bi-layout-text-window::before { content: "\f464"; }
+.bi-layout-three-columns::before { content: "\f465"; }
+.bi-layout-wtf::before { content: "\f466"; }
+.bi-life-preserver::before { content: "\f467"; }
+.bi-lightbulb-fill::before { content: "\f468"; }
+.bi-lightbulb-off-fill::before { content: "\f469"; }
+.bi-lightbulb-off::before { content: "\f46a"; }
+.bi-lightbulb::before { content: "\f46b"; }
+.bi-lightning-charge-fill::before { content: "\f46c"; }
+.bi-lightning-charge::before { content: "\f46d"; }
+.bi-lightning-fill::before { content: "\f46e"; }
+.bi-lightning::before { content: "\f46f"; }
+.bi-link-45deg::before { content: "\f470"; }
+.bi-link::before { content: "\f471"; }
+.bi-linkedin::before { content: "\f472"; }
+.bi-list-check::before { content: "\f473"; }
+.bi-list-nested::before { content: "\f474"; }
+.bi-list-ol::before { content: "\f475"; }
+.bi-list-stars::before { content: "\f476"; }
+.bi-list-task::before { content: "\f477"; }
+.bi-list-ul::before { content: "\f478"; }
+.bi-list::before { content: "\f479"; }
+.bi-lock-fill::before { content: "\f47a"; }
+.bi-lock::before { content: "\f47b"; }
+.bi-mailbox::before { content: "\f47c"; }
+.bi-mailbox2::before { content: "\f47d"; }
+.bi-map-fill::before { content: "\f47e"; }
+.bi-map::before { content: "\f47f"; }
+.bi-markdown-fill::before { content: "\f480"; }
+.bi-markdown::before { content: "\f481"; }
+.bi-mask::before { content: "\f482"; }
+.bi-megaphone-fill::before { content: "\f483"; }
+.bi-megaphone::before { content: "\f484"; }
+.bi-menu-app-fill::before { content: "\f485"; }
+.bi-menu-app::before { content: "\f486"; }
+.bi-menu-button-fill::before { content: "\f487"; }
+.bi-menu-button-wide-fill::before { content: "\f488"; }
+.bi-menu-button-wide::before { content: "\f489"; }
+.bi-menu-button::before { content: "\f48a"; }
+.bi-menu-down::before { content: "\f48b"; }
+.bi-menu-up::before { content: "\f48c"; }
+.bi-mic-fill::before { content: "\f48d"; }
+.bi-mic-mute-fill::before { content: "\f48e"; }
+.bi-mic-mute::before { content: "\f48f"; }
+.bi-mic::before { content: "\f490"; }
+.bi-minecart-loaded::before { content: "\f491"; }
+.bi-minecart::before { content: "\f492"; }
+.bi-moisture::before { content: "\f493"; }
+.bi-moon-fill::before { content: "\f494"; }
+.bi-moon-stars-fill::before { content: "\f495"; }
+.bi-moon-stars::before { content: "\f496"; }
+.bi-moon::before { content: "\f497"; }
+.bi-mouse-fill::before { content: "\f498"; }
+.bi-mouse::before { content: "\f499"; }
+.bi-mouse2-fill::before { content: "\f49a"; }
+.bi-mouse2::before { content: "\f49b"; }
+.bi-mouse3-fill::before { content: "\f49c"; }
+.bi-mouse3::before { content: "\f49d"; }
+.bi-music-note-beamed::before { content: "\f49e"; }
+.bi-music-note-list::before { content: "\f49f"; }
+.bi-music-note::before { content: "\f4a0"; }
+.bi-music-player-fill::before { content: "\f4a1"; }
+.bi-music-player::before { content: "\f4a2"; }
+.bi-newspaper::before { content: "\f4a3"; }
+.bi-node-minus-fill::before { content: "\f4a4"; }
+.bi-node-minus::before { content: "\f4a5"; }
+.bi-node-plus-fill::before { content: "\f4a6"; }
+.bi-node-plus::before { content: "\f4a7"; }
+.bi-nut-fill::before { content: "\f4a8"; }
+.bi-nut::before { content: "\f4a9"; }
+.bi-octagon-fill::before { content: "\f4aa"; }
+.bi-octagon-half::before { content: "\f4ab"; }
+.bi-octagon::before { content: "\f4ac"; }
+.bi-option::before { content: "\f4ad"; }
+.bi-outlet::before { content: "\f4ae"; }
+.bi-paint-bucket::before { content: "\f4af"; }
+.bi-palette-fill::before { content: "\f4b0"; }
+.bi-palette::before { content: "\f4b1"; }
+.bi-palette2::before { content: "\f4b2"; }
+.bi-paperclip::before { content: "\f4b3"; }
+.bi-paragraph::before { content: "\f4b4"; }
+.bi-patch-check-fill::before { content: "\f4b5"; }
+.bi-patch-check::before { content: "\f4b6"; }
+.bi-patch-exclamation-fill::before { content: "\f4b7"; }
+.bi-patch-exclamation::before { content: "\f4b8"; }
+.bi-patch-minus-fill::before { content: "\f4b9"; }
+.bi-patch-minus::before { content: "\f4ba"; }
+.bi-patch-plus-fill::before { content: "\f4bb"; }
+.bi-patch-plus::before { content: "\f4bc"; }
+.bi-patch-question-fill::before { content: "\f4bd"; }
+.bi-patch-question::before { content: "\f4be"; }
+.bi-pause-btn-fill::before { content: "\f4bf"; }
+.bi-pause-btn::before { content: "\f4c0"; }
+.bi-pause-circle-fill::before { content: "\f4c1"; }
+.bi-pause-circle::before { content: "\f4c2"; }
+.bi-pause-fill::before { content: "\f4c3"; }
+.bi-pause::before { content: "\f4c4"; }
+.bi-peace-fill::before { content: "\f4c5"; }
+.bi-peace::before { content: "\f4c6"; }
+.bi-pen-fill::before { content: "\f4c7"; }
+.bi-pen::before { content: "\f4c8"; }
+.bi-pencil-fill::before { content: "\f4c9"; }
+.bi-pencil-square::before { content: "\f4ca"; }
+.bi-pencil::before { content: "\f4cb"; }
+.bi-pentagon-fill::before { content: "\f4cc"; }
+.bi-pentagon-half::before { content: "\f4cd"; }
+.bi-pentagon::before { content: "\f4ce"; }
+.bi-people-fill::before { content: "\f4cf"; }
+.bi-people::before { content: "\f4d0"; }
+.bi-percent::before { content: "\f4d1"; }
+.bi-person-badge-fill::before { content: "\f4d2"; }
+.bi-person-badge::before { content: "\f4d3"; }
+.bi-person-bounding-box::before { content: "\f4d4"; }
+.bi-person-check-fill::before { content: "\f4d5"; }
+.bi-person-check::before { content: "\f4d6"; }
+.bi-person-circle::before { content: "\f4d7"; }
+.bi-person-dash-fill::before { content: "\f4d8"; }
+.bi-person-dash::before { content: "\f4d9"; }
+.bi-person-fill::before { content: "\f4da"; }
+.bi-person-lines-fill::before { content: "\f4db"; }
+.bi-person-plus-fill::before { content: "\f4dc"; }
+.bi-person-plus::before { content: "\f4dd"; }
+.bi-person-square::before { content: "\f4de"; }
+.bi-person-x-fill::before { content: "\f4df"; }
+.bi-person-x::before { content: "\f4e0"; }
+.bi-person::before { content: "\f4e1"; }
+.bi-phone-fill::before { content: "\f4e2"; }
+.bi-phone-landscape-fill::before { content: "\f4e3"; }
+.bi-phone-landscape::before { content: "\f4e4"; }
+.bi-phone-vibrate-fill::before { content: "\f4e5"; }
+.bi-phone-vibrate::before { content: "\f4e6"; }
+.bi-phone::before { content: "\f4e7"; }
+.bi-pie-chart-fill::before { content: "\f4e8"; }
+.bi-pie-chart::before { content: "\f4e9"; }
+.bi-pin-angle-fill::before { content: "\f4ea"; }
+.bi-pin-angle::before { content: "\f4eb"; }
+.bi-pin-fill::before { content: "\f4ec"; }
+.bi-pin::before { content: "\f4ed"; }
+.bi-pip-fill::before { content: "\f4ee"; }
+.bi-pip::before { content: "\f4ef"; }
+.bi-play-btn-fill::before { content: "\f4f0"; }
+.bi-play-btn::before { content: "\f4f1"; }
+.bi-play-circle-fill::before { content: "\f4f2"; }
+.bi-play-circle::before { content: "\f4f3"; }
+.bi-play-fill::before { content: "\f4f4"; }
+.bi-play::before { content: "\f4f5"; }
+.bi-plug-fill::before { content: "\f4f6"; }
+.bi-plug::before { content: "\f4f7"; }
+.bi-plus-circle-dotted::before { content: "\f4f8"; }
+.bi-plus-circle-fill::before { content: "\f4f9"; }
+.bi-plus-circle::before { content: "\f4fa"; }
+.bi-plus-square-dotted::before { content: "\f4fb"; }
+.bi-plus-square-fill::before { content: "\f4fc"; }
+.bi-plus-square::before { content: "\f4fd"; }
+.bi-plus::before { content: "\f4fe"; }
+.bi-power::before { content: "\f4ff"; }
+.bi-printer-fill::before { content: "\f500"; }
+.bi-printer::before { content: "\f501"; }
+.bi-puzzle-fill::before { content: "\f502"; }
+.bi-puzzle::before { content: "\f503"; }
+.bi-question-circle-fill::before { content: "\f504"; }
+.bi-question-circle::before { content: "\f505"; }
+.bi-question-diamond-fill::before { content: "\f506"; }
+.bi-question-diamond::before { content: "\f507"; }
+.bi-question-octagon-fill::before { content: "\f508"; }
+.bi-question-octagon::before { content: "\f509"; }
+.bi-question-square-fill::before { content: "\f50a"; }
+.bi-question-square::before { content: "\f50b"; }
+.bi-question::before { content: "\f50c"; }
+.bi-rainbow::before { content: "\f50d"; }
+.bi-receipt-cutoff::before { content: "\f50e"; }
+.bi-receipt::before { content: "\f50f"; }
+.bi-reception-0::before { content: "\f510"; }
+.bi-reception-1::before { content: "\f511"; }
+.bi-reception-2::before { content: "\f512"; }
+.bi-reception-3::before { content: "\f513"; }
+.bi-reception-4::before { content: "\f514"; }
+.bi-record-btn-fill::before { content: "\f515"; }
+.bi-record-btn::before { content: "\f516"; }
+.bi-record-circle-fill::before { content: "\f517"; }
+.bi-record-circle::before { content: "\f518"; }
+.bi-record-fill::before { content: "\f519"; }
+.bi-record::before { content: "\f51a"; }
+.bi-record2-fill::before { content: "\f51b"; }
+.bi-record2::before { content: "\f51c"; }
+.bi-reply-all-fill::before { content: "\f51d"; }
+.bi-reply-all::before { content: "\f51e"; }
+.bi-reply-fill::before { content: "\f51f"; }
+.bi-reply::before { content: "\f520"; }
+.bi-rss-fill::before { content: "\f521"; }
+.bi-rss::before { content: "\f522"; }
+.bi-rulers::before { content: "\f523"; }
+.bi-save-fill::before { content: "\f524"; }
+.bi-save::before { content: "\f525"; }
+.bi-save2-fill::before { content: "\f526"; }
+.bi-save2::before { content: "\f527"; }
+.bi-scissors::before { content: "\f528"; }
+.bi-screwdriver::before { content: "\f529"; }
+.bi-search::before { content: "\f52a"; }
+.bi-segmented-nav::before { content: "\f52b"; }
+.bi-server::before { content: "\f52c"; }
+.bi-share-fill::before { content: "\f52d"; }
+.bi-share::before { content: "\f52e"; }
+.bi-shield-check::before { content: "\f52f"; }
+.bi-shield-exclamation::before { content: "\f530"; }
+.bi-shield-fill-check::before { content: "\f531"; }
+.bi-shield-fill-exclamation::before { content: "\f532"; }
+.bi-shield-fill-minus::before { content: "\f533"; }
+.bi-shield-fill-plus::before { content: "\f534"; }
+.bi-shield-fill-x::before { content: "\f535"; }
+.bi-shield-fill::before { content: "\f536"; }
+.bi-shield-lock-fill::before { content: "\f537"; }
+.bi-shield-lock::before { content: "\f538"; }
+.bi-shield-minus::before { content: "\f539"; }
+.bi-shield-plus::before { content: "\f53a"; }
+.bi-shield-shaded::before { content: "\f53b"; }
+.bi-shield-slash-fill::before { content: "\f53c"; }
+.bi-shield-slash::before { content: "\f53d"; }
+.bi-shield-x::before { content: "\f53e"; }
+.bi-shield::before { content: "\f53f"; }
+.bi-shift-fill::before { content: "\f540"; }
+.bi-shift::before { content: "\f541"; }
+.bi-shop-window::before { content: "\f542"; }
+.bi-shop::before { content: "\f543"; }
+.bi-shuffle::before { content: "\f544"; }
+.bi-signpost-2-fill::before { content: "\f545"; }
+.bi-signpost-2::before { content: "\f546"; }
+.bi-signpost-fill::before { content: "\f547"; }
+.bi-signpost-split-fill::before { content: "\f548"; }
+.bi-signpost-split::before { content: "\f549"; }
+.bi-signpost::before { content: "\f54a"; }
+.bi-sim-fill::before { content: "\f54b"; }
+.bi-sim::before { content: "\f54c"; }
+.bi-skip-backward-btn-fill::before { content: "\f54d"; }
+.bi-skip-backward-btn::before { content: "\f54e"; }
+.bi-skip-backward-circle-fill::before { content: "\f54f"; }
+.bi-skip-backward-circle::before { content: "\f550"; }
+.bi-skip-backward-fill::before { content: "\f551"; }
+.bi-skip-backward::before { content: "\f552"; }
+.bi-skip-end-btn-fill::before { content: "\f553"; }
+.bi-skip-end-btn::before { content: "\f554"; }
+.bi-skip-end-circle-fill::before { content: "\f555"; }
+.bi-skip-end-circle::before { content: "\f556"; }
+.bi-skip-end-fill::before { content: "\f557"; }
+.bi-skip-end::before { content: "\f558"; }
+.bi-skip-forward-btn-fill::before { content: "\f559"; }
+.bi-skip-forward-btn::before { content: "\f55a"; }
+.bi-skip-forward-circle-fill::before { content: "\f55b"; }
+.bi-skip-forward-circle::before { content: "\f55c"; }
+.bi-skip-forward-fill::before { content: "\f55d"; }
+.bi-skip-forward::before { content: "\f55e"; }
+.bi-skip-start-btn-fill::before { content: "\f55f"; }
+.bi-skip-start-btn::before { content: "\f560"; }
+.bi-skip-start-circle-fill::before { content: "\f561"; }
+.bi-skip-start-circle::before { content: "\f562"; }
+.bi-skip-start-fill::before { content: "\f563"; }
+.bi-skip-start::before { content: "\f564"; }
+.bi-slack::before { content: "\f565"; }
+.bi-slash-circle-fill::before { content: "\f566"; }
+.bi-slash-circle::before { content: "\f567"; }
+.bi-slash-square-fill::before { content: "\f568"; }
+.bi-slash-square::before { content: "\f569"; }
+.bi-slash::before { content: "\f56a"; }
+.bi-sliders::before { content: "\f56b"; }
+.bi-smartwatch::before { content: "\f56c"; }
+.bi-snow::before { content: "\f56d"; }
+.bi-snow2::before { content: "\f56e"; }
+.bi-snow3::before { content: "\f56f"; }
+.bi-sort-alpha-down-alt::before { content: "\f570"; }
+.bi-sort-alpha-down::before { content: "\f571"; }
+.bi-sort-alpha-up-alt::before { content: "\f572"; }
+.bi-sort-alpha-up::before { content: "\f573"; }
+.bi-sort-down-alt::before { content: "\f574"; }
+.bi-sort-down::before { content: "\f575"; }
+.bi-sort-numeric-down-alt::before { content: "\f576"; }
+.bi-sort-numeric-down::before { content: "\f577"; }
+.bi-sort-numeric-up-alt::before { content: "\f578"; }
+.bi-sort-numeric-up::before { content: "\f579"; }
+.bi-sort-up-alt::before { content: "\f57a"; }
+.bi-sort-up::before { content: "\f57b"; }
+.bi-soundwave::before { content: "\f57c"; }
+.bi-speaker-fill::before { content: "\f57d"; }
+.bi-speaker::before { content: "\f57e"; }
+.bi-speedometer::before { content: "\f57f"; }
+.bi-speedometer2::before { content: "\f580"; }
+.bi-spellcheck::before { content: "\f581"; }
+.bi-square-fill::before { content: "\f582"; }
+.bi-square-half::before { content: "\f583"; }
+.bi-square::before { content: "\f584"; }
+.bi-stack::before { content: "\f585"; }
+.bi-star-fill::before { content: "\f586"; }
+.bi-star-half::before { content: "\f587"; }
+.bi-star::before { content: "\f588"; }
+.bi-stars::before { content: "\f589"; }
+.bi-stickies-fill::before { content: "\f58a"; }
+.bi-stickies::before { content: "\f58b"; }
+.bi-sticky-fill::before { content: "\f58c"; }
+.bi-sticky::before { content: "\f58d"; }
+.bi-stop-btn-fill::before { content: "\f58e"; }
+.bi-stop-btn::before { content: "\f58f"; }
+.bi-stop-circle-fill::before { content: "\f590"; }
+.bi-stop-circle::before { content: "\f591"; }
+.bi-stop-fill::before { content: "\f592"; }
+.bi-stop::before { content: "\f593"; }
+.bi-stoplights-fill::before { content: "\f594"; }
+.bi-stoplights::before { content: "\f595"; }
+.bi-stopwatch-fill::before { content: "\f596"; }
+.bi-stopwatch::before { content: "\f597"; }
+.bi-subtract::before { content: "\f598"; }
+.bi-suit-club-fill::before { content: "\f599"; }
+.bi-suit-club::before { content: "\f59a"; }
+.bi-suit-diamond-fill::before { content: "\f59b"; }
+.bi-suit-diamond::before { content: "\f59c"; }
+.bi-suit-heart-fill::before { content: "\f59d"; }
+.bi-suit-heart::before { content: "\f59e"; }
+.bi-suit-spade-fill::before { content: "\f59f"; }
+.bi-suit-spade::before { content: "\f5a0"; }
+.bi-sun-fill::before { content: "\f5a1"; }
+.bi-sun::before { content: "\f5a2"; }
+.bi-sunglasses::before { content: "\f5a3"; }
+.bi-sunrise-fill::before { content: "\f5a4"; }
+.bi-sunrise::before { content: "\f5a5"; }
+.bi-sunset-fill::before { content: "\f5a6"; }
+.bi-sunset::before { content: "\f5a7"; }
+.bi-symmetry-horizontal::before { content: "\f5a8"; }
+.bi-symmetry-vertical::before { content: "\f5a9"; }
+.bi-table::before { content: "\f5aa"; }
+.bi-tablet-fill::before { content: "\f5ab"; }
+.bi-tablet-landscape-fill::before { content: "\f5ac"; }
+.bi-tablet-landscape::before { content: "\f5ad"; }
+.bi-tablet::before { content: "\f5ae"; }
+.bi-tag-fill::before { content: "\f5af"; }
+.bi-tag::before { content: "\f5b0"; }
+.bi-tags-fill::before { content: "\f5b1"; }
+.bi-tags::before { content: "\f5b2"; }
+.bi-telegram::before { content: "\f5b3"; }
+.bi-telephone-fill::before { content: "\f5b4"; }
+.bi-telephone-forward-fill::before { content: "\f5b5"; }
+.bi-telephone-forward::before { content: "\f5b6"; }
+.bi-telephone-inbound-fill::before { content: "\f5b7"; }
+.bi-telephone-inbound::before { content: "\f5b8"; }
+.bi-telephone-minus-fill::before { content: "\f5b9"; }
+.bi-telephone-minus::before { content: "\f5ba"; }
+.bi-telephone-outbound-fill::before { content: "\f5bb"; }
+.bi-telephone-outbound::before { content: "\f5bc"; }
+.bi-telephone-plus-fill::before { content: "\f5bd"; }
+.bi-telephone-plus::before { content: "\f5be"; }
+.bi-telephone-x-fill::before { content: "\f5bf"; }
+.bi-telephone-x::before { content: "\f5c0"; }
+.bi-telephone::before { content: "\f5c1"; }
+.bi-terminal-fill::before { content: "\f5c2"; }
+.bi-terminal::before { content: "\f5c3"; }
+.bi-text-center::before { content: "\f5c4"; }
+.bi-text-indent-left::before { content: "\f5c5"; }
+.bi-text-indent-right::before { content: "\f5c6"; }
+.bi-text-left::before { content: "\f5c7"; }
+.bi-text-paragraph::before { content: "\f5c8"; }
+.bi-text-right::before { content: "\f5c9"; }
+.bi-textarea-resize::before { content: "\f5ca"; }
+.bi-textarea-t::before { content: "\f5cb"; }
+.bi-textarea::before { content: "\f5cc"; }
+.bi-thermometer-half::before { content: "\f5cd"; }
+.bi-thermometer-high::before { content: "\f5ce"; }
+.bi-thermometer-low::before { content: "\f5cf"; }
+.bi-thermometer-snow::before { content: "\f5d0"; }
+.bi-thermometer-sun::before { content: "\f5d1"; }
+.bi-thermometer::before { content: "\f5d2"; }
+.bi-three-dots-vertical::before { content: "\f5d3"; }
+.bi-three-dots::before { content: "\f5d4"; }
+.bi-toggle-off::before { content: "\f5d5"; }
+.bi-toggle-on::before { content: "\f5d6"; }
+.bi-toggle2-off::before { content: "\f5d7"; }
+.bi-toggle2-on::before { content: "\f5d8"; }
+.bi-toggles::before { content: "\f5d9"; }
+.bi-toggles2::before { content: "\f5da"; }
+.bi-tools::before { content: "\f5db"; }
+.bi-tornado::before { content: "\f5dc"; }
+.bi-trash-fill::before { content: "\f5dd"; }
+.bi-trash::before { content: "\f5de"; }
+.bi-trash2-fill::before { content: "\f5df"; }
+.bi-trash2::before { content: "\f5e0"; }
+.bi-tree-fill::before { content: "\f5e1"; }
+.bi-tree::before { content: "\f5e2"; }
+.bi-triangle-fill::before { content: "\f5e3"; }
+.bi-triangle-half::before { content: "\f5e4"; }
+.bi-triangle::before { content: "\f5e5"; }
+.bi-trophy-fill::before { content: "\f5e6"; }
+.bi-trophy::before { content: "\f5e7"; }
+.bi-tropical-storm::before { content: "\f5e8"; }
+.bi-truck-flatbed::before { content: "\f5e9"; }
+.bi-truck::before { content: "\f5ea"; }
+.bi-tsunami::before { content: "\f5eb"; }
+.bi-tv-fill::before { content: "\f5ec"; }
+.bi-tv::before { content: "\f5ed"; }
+.bi-twitch::before { content: "\f5ee"; }
+.bi-twitter::before { content: "\f5ef"; }
+.bi-type-bold::before { content: "\f5f0"; }
+.bi-type-h1::before { content: "\f5f1"; }
+.bi-type-h2::before { content: "\f5f2"; }
+.bi-type-h3::before { content: "\f5f3"; }
+.bi-type-italic::before { content: "\f5f4"; }
+.bi-type-strikethrough::before { content: "\f5f5"; }
+.bi-type-underline::before { content: "\f5f6"; }
+.bi-type::before { content: "\f5f7"; }
+.bi-ui-checks-grid::before { content: "\f5f8"; }
+.bi-ui-checks::before { content: "\f5f9"; }
+.bi-ui-radios-grid::before { content: "\f5fa"; }
+.bi-ui-radios::before { content: "\f5fb"; }
+.bi-umbrella-fill::before { content: "\f5fc"; }
+.bi-umbrella::before { content: "\f5fd"; }
+.bi-union::before { content: "\f5fe"; }
+.bi-unlock-fill::before { content: "\f5ff"; }
+.bi-unlock::before { content: "\f600"; }
+.bi-upc-scan::before { content: "\f601"; }
+.bi-upc::before { content: "\f602"; }
+.bi-upload::before { content: "\f603"; }
+.bi-vector-pen::before { content: "\f604"; }
+.bi-view-list::before { content: "\f605"; }
+.bi-view-stacked::before { content: "\f606"; }
+.bi-vinyl-fill::before { content: "\f607"; }
+.bi-vinyl::before { content: "\f608"; }
+.bi-voicemail::before { content: "\f609"; }
+.bi-volume-down-fill::before { content: "\f60a"; }
+.bi-volume-down::before { content: "\f60b"; }
+.bi-volume-mute-fill::before { content: "\f60c"; }
+.bi-volume-mute::before { content: "\f60d"; }
+.bi-volume-off-fill::before { content: "\f60e"; }
+.bi-volume-off::before { content: "\f60f"; }
+.bi-volume-up-fill::before { content: "\f610"; }
+.bi-volume-up::before { content: "\f611"; }
+.bi-vr::before { content: "\f612"; }
+.bi-wallet-fill::before { content: "\f613"; }
+.bi-wallet::before { content: "\f614"; }
+.bi-wallet2::before { content: "\f615"; }
+.bi-watch::before { content: "\f616"; }
+.bi-water::before { content: "\f617"; }
+.bi-whatsapp::before { content: "\f618"; }
+.bi-wifi-1::before { content: "\f619"; }
+.bi-wifi-2::before { content: "\f61a"; }
+.bi-wifi-off::before { content: "\f61b"; }
+.bi-wifi::before { content: "\f61c"; }
+.bi-wind::before { content: "\f61d"; }
+.bi-window-dock::before { content: "\f61e"; }
+.bi-window-sidebar::before { content: "\f61f"; }
+.bi-window::before { content: "\f620"; }
+.bi-wrench::before { content: "\f621"; }
+.bi-x-circle-fill::before { content: "\f622"; }
+.bi-x-circle::before { content: "\f623"; }
+.bi-x-diamond-fill::before { content: "\f624"; }
+.bi-x-diamond::before { content: "\f625"; }
+.bi-x-octagon-fill::before { content: "\f626"; }
+.bi-x-octagon::before { content: "\f627"; }
+.bi-x-square-fill::before { content: "\f628"; }
+.bi-x-square::before { content: "\f629"; }
+.bi-x::before { content: "\f62a"; }
+.bi-youtube::before { content: "\f62b"; }
+.bi-zoom-in::before { content: "\f62c"; }
+.bi-zoom-out::before { content: "\f62d"; }
+.bi-bank::before { content: "\f62e"; }
+.bi-bank2::before { content: "\f62f"; }
+.bi-bell-slash-fill::before { content: "\f630"; }
+.bi-bell-slash::before { content: "\f631"; }
+.bi-cash-coin::before { content: "\f632"; }
+.bi-check-lg::before { content: "\f633"; }
+.bi-coin::before { content: "\f634"; }
+.bi-currency-bitcoin::before { content: "\f635"; }
+.bi-currency-dollar::before { content: "\f636"; }
+.bi-currency-euro::before { content: "\f637"; }
+.bi-currency-exchange::before { content: "\f638"; }
+.bi-currency-pound::before { content: "\f639"; }
+.bi-currency-yen::before { content: "\f63a"; }
+.bi-dash-lg::before { content: "\f63b"; }
+.bi-exclamation-lg::before { content: "\f63c"; }
+.bi-file-earmark-pdf-fill::before { content: "\f63d"; }
+.bi-file-earmark-pdf::before { content: "\f63e"; }
+.bi-file-pdf-fill::before { content: "\f63f"; }
+.bi-file-pdf::before { content: "\f640"; }
+.bi-gender-ambiguous::before { content: "\f641"; }
+.bi-gender-female::before { content: "\f642"; }
+.bi-gender-male::before { content: "\f643"; }
+.bi-gender-trans::before { content: "\f644"; }
+.bi-headset-vr::before { content: "\f645"; }
+.bi-info-lg::before { content: "\f646"; }
+.bi-mastodon::before { content: "\f647"; }
+.bi-messenger::before { content: "\f648"; }
+.bi-piggy-bank-fill::before { content: "\f649"; }
+.bi-piggy-bank::before { content: "\f64a"; }
+.bi-pin-map-fill::before { content: "\f64b"; }
+.bi-pin-map::before { content: "\f64c"; }
+.bi-plus-lg::before { content: "\f64d"; }
+.bi-question-lg::before { content: "\f64e"; }
+.bi-recycle::before { content: "\f64f"; }
+.bi-reddit::before { content: "\f650"; }
+.bi-safe-fill::before { content: "\f651"; }
+.bi-safe2-fill::before { content: "\f652"; }
+.bi-safe2::before { content: "\f653"; }
+.bi-sd-card-fill::before { content: "\f654"; }
+.bi-sd-card::before { content: "\f655"; }
+.bi-skype::before { content: "\f656"; }
+.bi-slash-lg::before { content: "\f657"; }
+.bi-translate::before { content: "\f658"; }
+.bi-x-lg::before { content: "\f659"; }
+.bi-safe::before { content: "\f65a"; }
+.bi-apple::before { content: "\f65b"; }
+.bi-microsoft::before { content: "\f65d"; }
+.bi-windows::before { content: "\f65e"; }
+.bi-behance::before { content: "\f65c"; }
+.bi-dribbble::before { content: "\f65f"; }
+.bi-line::before { content: "\f660"; }
+.bi-medium::before { content: "\f661"; }
+.bi-paypal::before { content: "\f662"; }
+.bi-pinterest::before { content: "\f663"; }
+.bi-signal::before { content: "\f664"; }
+.bi-snapchat::before { content: "\f665"; }
+.bi-spotify::before { content: "\f666"; }
+.bi-stack-overflow::before { content: "\f667"; }
+.bi-strava::before { content: "\f668"; }
+.bi-wordpress::before { content: "\f669"; }
+.bi-vimeo::before { content: "\f66a"; }
+.bi-activity::before { content: "\f66b"; }
+.bi-easel2-fill::before { content: "\f66c"; }
+.bi-easel2::before { content: "\f66d"; }
+.bi-easel3-fill::before { content: "\f66e"; }
+.bi-easel3::before { content: "\f66f"; }
+.bi-fan::before { content: "\f670"; }
+.bi-fingerprint::before { content: "\f671"; }
+.bi-graph-down-arrow::before { content: "\f672"; }
+.bi-graph-up-arrow::before { content: "\f673"; }
+.bi-hypnotize::before { content: "\f674"; }
+.bi-magic::before { content: "\f675"; }
+.bi-person-rolodex::before { content: "\f676"; }
+.bi-person-video::before { content: "\f677"; }
+.bi-person-video2::before { content: "\f678"; }
+.bi-person-video3::before { content: "\f679"; }
+.bi-person-workspace::before { content: "\f67a"; }
+.bi-radioactive::before { content: "\f67b"; }
+.bi-webcam-fill::before { content: "\f67c"; }
+.bi-webcam::before { content: "\f67d"; }
+.bi-yin-yang::before { content: "\f67e"; }
+.bi-bandaid-fill::before { content: "\f680"; }
+.bi-bandaid::before { content: "\f681"; }
+.bi-bluetooth::before { content: "\f682"; }
+.bi-body-text::before { content: "\f683"; }
+.bi-boombox::before { content: "\f684"; }
+.bi-boxes::before { content: "\f685"; }
+.bi-dpad-fill::before { content: "\f686"; }
+.bi-dpad::before { content: "\f687"; }
+.bi-ear-fill::before { content: "\f688"; }
+.bi-ear::before { content: "\f689"; }
+.bi-envelope-check-1::before { content: "\f68a"; }
+.bi-envelope-check-fill::before { content: "\f68b"; }
+.bi-envelope-check::before { content: "\f68c"; }
+.bi-envelope-dash-1::before { content: "\f68d"; }
+.bi-envelope-dash-fill::before { content: "\f68e"; }
+.bi-envelope-dash::before { content: "\f68f"; }
+.bi-envelope-exclamation-1::before { content: "\f690"; }
+.bi-envelope-exclamation-fill::before { content: "\f691"; }
+.bi-envelope-exclamation::before { content: "\f692"; }
+.bi-envelope-plus-fill::before { content: "\f693"; }
+.bi-envelope-plus::before { content: "\f694"; }
+.bi-envelope-slash-1::before { content: "\f695"; }
+.bi-envelope-slash-fill::before { content: "\f696"; }
+.bi-envelope-slash::before { content: "\f697"; }
+.bi-envelope-x-1::before { content: "\f698"; }
+.bi-envelope-x-fill::before { content: "\f699"; }
+.bi-envelope-x::before { content: "\f69a"; }
+.bi-explicit-fill::before { content: "\f69b"; }
+.bi-explicit::before { content: "\f69c"; }
+.bi-git::before { content: "\f69d"; }
+.bi-infinity::before { content: "\f69e"; }
+.bi-list-columns-reverse::before { content: "\f69f"; }
+.bi-list-columns::before { content: "\f6a0"; }
+.bi-meta::before { content: "\f6a1"; }
+.bi-mortorboard-fill::before { content: "\f6a2"; }
+.bi-mortorboard::before { content: "\f6a3"; }
+.bi-nintendo-switch::before { content: "\f6a4"; }
+.bi-pc-display-horizontal::before { content: "\f6a5"; }
+.bi-pc-display::before { content: "\f6a6"; }
+.bi-pc-horizontal::before { content: "\f6a7"; }
+.bi-pc::before { content: "\f6a8"; }
+.bi-playstation::before { content: "\f6a9"; }
+.bi-plus-slash-minus::before { content: "\f6aa"; }
+.bi-projector-fill::before { content: "\f6ab"; }
+.bi-projector::before { content: "\f6ac"; }
+.bi-qr-code-scan::before { content: "\f6ad"; }
+.bi-qr-code::before { content: "\f6ae"; }
+.bi-quora::before { content: "\f6af"; }
+.bi-quote::before { content: "\f6b0"; }
+.bi-robot::before { content: "\f6b1"; }
+.bi-send-check-fill::before { content: "\f6b2"; }
+.bi-send-check::before { content: "\f6b3"; }
+.bi-send-dash-fill::before { content: "\f6b4"; }
+.bi-send-dash::before { content: "\f6b5"; }
+.bi-send-exclamation-1::before { content: "\f6b6"; }
+.bi-send-exclamation-fill::before { content: "\f6b7"; }
+.bi-send-exclamation::before { content: "\f6b8"; }
+.bi-send-fill::before { content: "\f6b9"; }
+.bi-send-plus-fill::before { content: "\f6ba"; }
+.bi-send-plus::before { content: "\f6bb"; }
+.bi-send-slash-fill::before { content: "\f6bc"; }
+.bi-send-slash::before { content: "\f6bd"; }
+.bi-send-x-fill::before { content: "\f6be"; }
+.bi-send-x::before { content: "\f6bf"; }
+.bi-send::before { content: "\f6c0"; }
+.bi-steam::before { content: "\f6c1"; }
+.bi-terminal-dash-1::before { content: "\f6c2"; }
+.bi-terminal-dash::before { content: "\f6c3"; }
+.bi-terminal-plus::before { content: "\f6c4"; }
+.bi-terminal-split::before { content: "\f6c5"; }
+.bi-ticket-detailed-fill::before { content: "\f6c6"; }
+.bi-ticket-detailed::before { content: "\f6c7"; }
+.bi-ticket-fill::before { content: "\f6c8"; }
+.bi-ticket-perforated-fill::before { content: "\f6c9"; }
+.bi-ticket-perforated::before { content: "\f6ca"; }
+.bi-ticket::before { content: "\f6cb"; }
+.bi-tiktok::before { content: "\f6cc"; }
+.bi-window-dash::before { content: "\f6cd"; }
+.bi-window-desktop::before { content: "\f6ce"; }
+.bi-window-fullscreen::before { content: "\f6cf"; }
+.bi-window-plus::before { content: "\f6d0"; }
+.bi-window-split::before { content: "\f6d1"; }
+.bi-window-stack::before { content: "\f6d2"; }
+.bi-window-x::before { content: "\f6d3"; }
+.bi-xbox::before { content: "\f6d4"; }
+.bi-ethernet::before { content: "\f6d5"; }
+.bi-hdmi-fill::before { content: "\f6d6"; }
+.bi-hdmi::before { content: "\f6d7"; }
+.bi-usb-c-fill::before { content: "\f6d8"; }
+.bi-usb-c::before { content: "\f6d9"; }
+.bi-usb-fill::before { content: "\f6da"; }
+.bi-usb-plug-fill::before { content: "\f6db"; }
+.bi-usb-plug::before { content: "\f6dc"; }
+.bi-usb-symbol::before { content: "\f6dd"; }
+.bi-usb::before { content: "\f6de"; }
+.bi-boombox-fill::before { content: "\f6df"; }
+.bi-displayport-1::before { content: "\f6e0"; }
+.bi-displayport::before { content: "\f6e1"; }
+.bi-gpu-card::before { content: "\f6e2"; }
+.bi-memory::before { content: "\f6e3"; }
+.bi-modem-fill::before { content: "\f6e4"; }
+.bi-modem::before { content: "\f6e5"; }
+.bi-motherboard-fill::before { content: "\f6e6"; }
+.bi-motherboard::before { content: "\f6e7"; }
+.bi-optical-audio-fill::before { content: "\f6e8"; }
+.bi-optical-audio::before { content: "\f6e9"; }
+.bi-pci-card::before { content: "\f6ea"; }
+.bi-router-fill::before { content: "\f6eb"; }
+.bi-router::before { content: "\f6ec"; }
+.bi-ssd-fill::before { content: "\f6ed"; }
+.bi-ssd::before { content: "\f6ee"; }
+.bi-thunderbolt-fill::before { content: "\f6ef"; }
+.bi-thunderbolt::before { content: "\f6f0"; }
+.bi-usb-drive-fill::before { content: "\f6f1"; }
+.bi-usb-drive::before { content: "\f6f2"; }
+.bi-usb-micro-fill::before { content: "\f6f3"; }
+.bi-usb-micro::before { content: "\f6f4"; }
+.bi-usb-mini-fill::before { content: "\f6f5"; }
+.bi-usb-mini::before { content: "\f6f6"; }
+.bi-cloud-haze2::before { content: "\f6f7"; }
+.bi-device-hdd-fill::before { content: "\f6f8"; }
+.bi-device-hdd::before { content: "\f6f9"; }
+.bi-device-ssd-fill::before { content: "\f6fa"; }
+.bi-device-ssd::before { content: "\f6fb"; }
+.bi-displayport-fill::before { content: "\f6fc"; }
+.bi-mortarboard-fill::before { content: "\f6fd"; }
+.bi-mortarboard::before { content: "\f6fe"; }
+.bi-terminal-x::before { content: "\f6ff"; }
+.bi-arrow-through-heart-fill::before { content: "\f700"; }
+.bi-arrow-through-heart::before { content: "\f701"; }
+.bi-badge-sd-fill::before { content: "\f702"; }
+.bi-badge-sd::before { content: "\f703"; }
+.bi-bag-heart-fill::before { content: "\f704"; }
+.bi-bag-heart::before { content: "\f705"; }
+.bi-balloon-fill::before { content: "\f706"; }
+.bi-balloon-heart-fill::before { content: "\f707"; }
+.bi-balloon-heart::before { content: "\f708"; }
+.bi-balloon::before { content: "\f709"; }
+.bi-box2-fill::before { content: "\f70a"; }
+.bi-box2-heart-fill::before { content: "\f70b"; }
+.bi-box2-heart::before { content: "\f70c"; }
+.bi-box2::before { content: "\f70d"; }
+.bi-braces-asterisk::before { content: "\f70e"; }
+.bi-calendar-heart-fill::before { content: "\f70f"; }
+.bi-calendar-heart::before { content: "\f710"; }
+.bi-calendar2-heart-fill::before { content: "\f711"; }
+.bi-calendar2-heart::before { content: "\f712"; }
+.bi-chat-heart-fill::before { content: "\f713"; }
+.bi-chat-heart::before { content: "\f714"; }
+.bi-chat-left-heart-fill::before { content: "\f715"; }
+.bi-chat-left-heart::before { content: "\f716"; }
+.bi-chat-right-heart-fill::before { content: "\f717"; }
+.bi-chat-right-heart::before { content: "\f718"; }
+.bi-chat-square-heart-fill::before { content: "\f719"; }
+.bi-chat-square-heart::before { content: "\f71a"; }
+.bi-clipboard-check-fill::before { content: "\f71b"; }
+.bi-clipboard-data-fill::before { content: "\f71c"; }
+.bi-clipboard-fill::before { content: "\f71d"; }
+.bi-clipboard-heart-fill::before { content: "\f71e"; }
+.bi-clipboard-heart::before { content: "\f71f"; }
+.bi-clipboard-minus-fill::before { content: "\f720"; }
+.bi-clipboard-plus-fill::before { content: "\f721"; }
+.bi-clipboard-pulse::before { content: "\f722"; }
+.bi-clipboard-x-fill::before { content: "\f723"; }
+.bi-clipboard2-check-fill::before { content: "\f724"; }
+.bi-clipboard2-check::before { content: "\f725"; }
+.bi-clipboard2-data-fill::before { content: "\f726"; }
+.bi-clipboard2-data::before { content: "\f727"; }
+.bi-clipboard2-fill::before { content: "\f728"; }
+.bi-clipboard2-heart-fill::before { content: "\f729"; }
+.bi-clipboard2-heart::before { content: "\f72a"; }
+.bi-clipboard2-minus-fill::before { content: "\f72b"; }
+.bi-clipboard2-minus::before { content: "\f72c"; }
+.bi-clipboard2-plus-fill::before { content: "\f72d"; }
+.bi-clipboard2-plus::before { content: "\f72e"; }
+.bi-clipboard2-pulse-fill::before { content: "\f72f"; }
+.bi-clipboard2-pulse::before { content: "\f730"; }
+.bi-clipboard2-x-fill::before { content: "\f731"; }
+.bi-clipboard2-x::before { content: "\f732"; }
+.bi-clipboard2::before { content: "\f733"; }
+.bi-emoji-kiss-fill::before { content: "\f734"; }
+.bi-emoji-kiss::before { content: "\f735"; }
+.bi-envelope-heart-fill::before { content: "\f736"; }
+.bi-envelope-heart::before { content: "\f737"; }
+.bi-envelope-open-heart-fill::before { content: "\f738"; }
+.bi-envelope-open-heart::before { content: "\f739"; }
+.bi-envelope-paper-fill::before { content: "\f73a"; }
+.bi-envelope-paper-heart-fill::before { content: "\f73b"; }
+.bi-envelope-paper-heart::before { content: "\f73c"; }
+.bi-envelope-paper::before { content: "\f73d"; }
+.bi-filetype-aac::before { content: "\f73e"; }
+.bi-filetype-ai::before { content: "\f73f"; }
+.bi-filetype-bmp::before { content: "\f740"; }
+.bi-filetype-cs::before { content: "\f741"; }
+.bi-filetype-css::before { content: "\f742"; }
+.bi-filetype-csv::before { content: "\f743"; }
+.bi-filetype-doc::before { content: "\f744"; }
+.bi-filetype-docx::before { content: "\f745"; }
+.bi-filetype-exe::before { content: "\f746"; }
+.bi-filetype-gif::before { content: "\f747"; }
+.bi-filetype-heic::before { content: "\f748"; }
+.bi-filetype-html::before { content: "\f749"; }
+.bi-filetype-java::before { content: "\f74a"; }
+.bi-filetype-jpg::before { content: "\f74b"; }
+.bi-filetype-js::before { content: "\f74c"; }
+.bi-filetype-jsx::before { content: "\f74d"; }
+.bi-filetype-key::before { content: "\f74e"; }
+.bi-filetype-m4p::before { content: "\f74f"; }
+.bi-filetype-md::before { content: "\f750"; }
+.bi-filetype-mdx::before { content: "\f751"; }
+.bi-filetype-mov::before { content: "\f752"; }
+.bi-filetype-mp3::before { content: "\f753"; }
+.bi-filetype-mp4::before { content: "\f754"; }
+.bi-filetype-otf::before { content: "\f755"; }
+.bi-filetype-pdf::before { content: "\f756"; }
+.bi-filetype-php::before { content: "\f757"; }
+.bi-filetype-png::before { content: "\f758"; }
+.bi-filetype-ppt-1::before { content: "\f759"; }
+.bi-filetype-ppt::before { content: "\f75a"; }
+.bi-filetype-psd::before { content: "\f75b"; }
+.bi-filetype-py::before { content: "\f75c"; }
+.bi-filetype-raw::before { content: "\f75d"; }
+.bi-filetype-rb::before { content: "\f75e"; }
+.bi-filetype-sass::before { content: "\f75f"; }
+.bi-filetype-scss::before { content: "\f760"; }
+.bi-filetype-sh::before { content: "\f761"; }
+.bi-filetype-svg::before { content: "\f762"; }
+.bi-filetype-tiff::before { content: "\f763"; }
+.bi-filetype-tsx::before { content: "\f764"; }
+.bi-filetype-ttf::before { content: "\f765"; }
+.bi-filetype-txt::before { content: "\f766"; }
+.bi-filetype-wav::before { content: "\f767"; }
+.bi-filetype-woff::before { content: "\f768"; }
+.bi-filetype-xls-1::before { content: "\f769"; }
+.bi-filetype-xls::before { content: "\f76a"; }
+.bi-filetype-xml::before { content: "\f76b"; }
+.bi-filetype-yml::before { content: "\f76c"; }
+.bi-heart-arrow::before { content: "\f76d"; }
+.bi-heart-pulse-fill::before { content: "\f76e"; }
+.bi-heart-pulse::before { content: "\f76f"; }
+.bi-heartbreak-fill::before { content: "\f770"; }
+.bi-heartbreak::before { content: "\f771"; }
+.bi-hearts::before { content: "\f772"; }
+.bi-hospital-fill::before { content: "\f773"; }
+.bi-hospital::before { content: "\f774"; }
+.bi-house-heart-fill::before { content: "\f775"; }
+.bi-house-heart::before { content: "\f776"; }
+.bi-incognito::before { content: "\f777"; }
+.bi-magnet-fill::before { content: "\f778"; }
+.bi-magnet::before { content: "\f779"; }
+.bi-person-heart::before { content: "\f77a"; }
+.bi-person-hearts::before { content: "\f77b"; }
+.bi-phone-flip::before { content: "\f77c"; }
+.bi-plugin::before { content: "\f77d"; }
+.bi-postage-fill::before { content: "\f77e"; }
+.bi-postage-heart-fill::before { content: "\f77f"; }
+.bi-postage-heart::before { content: "\f780"; }
+.bi-postage::before { content: "\f781"; }
+.bi-postcard-fill::before { content: "\f782"; }
+.bi-postcard-heart-fill::before { content: "\f783"; }
+.bi-postcard-heart::before { content: "\f784"; }
+.bi-postcard::before { content: "\f785"; }
+.bi-search-heart-fill::before { content: "\f786"; }
+.bi-search-heart::before { content: "\f787"; }
+.bi-sliders2-vertical::before { content: "\f788"; }
+.bi-sliders2::before { content: "\f789"; }
+.bi-trash3-fill::before { content: "\f78a"; }
+.bi-trash3::before { content: "\f78b"; }
+.bi-valentine::before { content: "\f78c"; }
+.bi-valentine2::before { content: "\f78d"; }
+.bi-wrench-adjustable-circle-fill::before { content: "\f78e"; }
+.bi-wrench-adjustable-circle::before { content: "\f78f"; }
+.bi-wrench-adjustable::before { content: "\f790"; }
+.bi-filetype-json::before { content: "\f791"; }
+.bi-filetype-pptx::before { content: "\f792"; }
+.bi-filetype-xlsx::before { content: "\f793"; }
+.bi-1-circle-1::before { content: "\f794"; }
+.bi-1-circle-fill-1::before { content: "\f795"; }
+.bi-1-circle-fill::before { content: "\f796"; }
+.bi-1-circle::before { content: "\f797"; }
+.bi-1-square-fill::before { content: "\f798"; }
+.bi-1-square::before { content: "\f799"; }
+.bi-2-circle-1::before { content: "\f79a"; }
+.bi-2-circle-fill-1::before { content: "\f79b"; }
+.bi-2-circle-fill::before { content: "\f79c"; }
+.bi-2-circle::before { content: "\f79d"; }
+.bi-2-square-fill::before { content: "\f79e"; }
+.bi-2-square::before { content: "\f79f"; }
+.bi-3-circle-1::before { content: "\f7a0"; }
+.bi-3-circle-fill-1::before { content: "\f7a1"; }
+.bi-3-circle-fill::before { content: "\f7a2"; }
+.bi-3-circle::before { content: "\f7a3"; }
+.bi-3-square-fill::before { content: "\f7a4"; }
+.bi-3-square::before { content: "\f7a5"; }
+.bi-4-circle-1::before { content: "\f7a6"; }
+.bi-4-circle-fill-1::before { content: "\f7a7"; }
+.bi-4-circle-fill::before { content: "\f7a8"; }
+.bi-4-circle::before { content: "\f7a9"; }
+.bi-4-square-fill::before { content: "\f7aa"; }
+.bi-4-square::before { content: "\f7ab"; }
+.bi-5-circle-1::before { content: "\f7ac"; }
+.bi-5-circle-fill-1::before { content: "\f7ad"; }
+.bi-5-circle-fill::before { content: "\f7ae"; }
+.bi-5-circle::before { content: "\f7af"; }
+.bi-5-square-fill::before { content: "\f7b0"; }
+.bi-5-square::before { content: "\f7b1"; }
+.bi-6-circle-1::before { content: "\f7b2"; }
+.bi-6-circle-fill-1::before { content: "\f7b3"; }
+.bi-6-circle-fill::before { content: "\f7b4"; }
+.bi-6-circle::before { content: "\f7b5"; }
+.bi-6-square-fill::before { content: "\f7b6"; }
+.bi-6-square::before { content: "\f7b7"; }
+.bi-7-circle-1::before { content: "\f7b8"; }
+.bi-7-circle-fill-1::before { content: "\f7b9"; }
+.bi-7-circle-fill::before { content: "\f7ba"; }
+.bi-7-circle::before { content: "\f7bb"; }
+.bi-7-square-fill::before { content: "\f7bc"; }
+.bi-7-square::before { content: "\f7bd"; }
+.bi-8-circle-1::before { content: "\f7be"; }
+.bi-8-circle-fill-1::before { content: "\f7bf"; }
+.bi-8-circle-fill::before { content: "\f7c0"; }
+.bi-8-circle::before { content: "\f7c1"; }
+.bi-8-square-fill::before { content: "\f7c2"; }
+.bi-8-square::before { content: "\f7c3"; }
+.bi-9-circle-1::before { content: "\f7c4"; }
+.bi-9-circle-fill-1::before { content: "\f7c5"; }
+.bi-9-circle-fill::before { content: "\f7c6"; }
+.bi-9-circle::before { content: "\f7c7"; }
+.bi-9-square-fill::before { content: "\f7c8"; }
+.bi-9-square::before { content: "\f7c9"; }
+.bi-airplane-engines-fill::before { content: "\f7ca"; }
+.bi-airplane-engines::before { content: "\f7cb"; }
+.bi-airplane-fill::before { content: "\f7cc"; }
+.bi-airplane::before { content: "\f7cd"; }
+.bi-alexa::before { content: "\f7ce"; }
+.bi-alipay::before { content: "\f7cf"; }
+.bi-android::before { content: "\f7d0"; }
+.bi-android2::before { content: "\f7d1"; }
+.bi-box-fill::before { content: "\f7d2"; }
+.bi-box-seam-fill::before { content: "\f7d3"; }
+.bi-browser-chrome::before { content: "\f7d4"; }
+.bi-browser-edge::before { content: "\f7d5"; }
+.bi-browser-firefox::before { content: "\f7d6"; }
+.bi-browser-safari::before { content: "\f7d7"; }
+.bi-c-circle-1::before { content: "\f7d8"; }
+.bi-c-circle-fill-1::before { content: "\f7d9"; }
+.bi-c-circle-fill::before { content: "\f7da"; }
+.bi-c-circle::before { content: "\f7db"; }
+.bi-c-square-fill::before { content: "\f7dc"; }
+.bi-c-square::before { content: "\f7dd"; }
+.bi-capsule-pill::before { content: "\f7de"; }
+.bi-capsule::before { content: "\f7df"; }
+.bi-car-front-fill::before { content: "\f7e0"; }
+.bi-car-front::before { content: "\f7e1"; }
+.bi-cassette-fill::before { content: "\f7e2"; }
+.bi-cassette::before { content: "\f7e3"; }
+.bi-cc-circle-1::before { content: "\f7e4"; }
+.bi-cc-circle-fill-1::before { content: "\f7e5"; }
+.bi-cc-circle-fill::before { content: "\f7e6"; }
+.bi-cc-circle::before { content: "\f7e7"; }
+.bi-cc-square-fill::before { content: "\f7e8"; }
+.bi-cc-square::before { content: "\f7e9"; }
+.bi-cup-hot-fill::before { content: "\f7ea"; }
+.bi-cup-hot::before { content: "\f7eb"; }
+.bi-currency-rupee::before { content: "\f7ec"; }
+.bi-dropbox::before { content: "\f7ed"; }
+.bi-escape::before { content: "\f7ee"; }
+.bi-fast-forward-btn-fill::before { content: "\f7ef"; }
+.bi-fast-forward-btn::before { content: "\f7f0"; }
+.bi-fast-forward-circle-fill::before { content: "\f7f1"; }
+.bi-fast-forward-circle::before { content: "\f7f2"; }
+.bi-fast-forward-fill::before { content: "\f7f3"; }
+.bi-fast-forward::before { content: "\f7f4"; }
+.bi-filetype-sql::before { content: "\f7f5"; }
+.bi-fire::before { content: "\f7f6"; }
+.bi-google-play::before { content: "\f7f7"; }
+.bi-h-circle-1::before { content: "\f7f8"; }
+.bi-h-circle-fill-1::before { content: "\f7f9"; }
+.bi-h-circle-fill::before { content: "\f7fa"; }
+.bi-h-circle::before { content: "\f7fb"; }
+.bi-h-square-fill::before { content: "\f7fc"; }
+.bi-h-square::before { content: "\f7fd"; }
+.bi-indent::before { content: "\f7fe"; }
+.bi-lungs-fill::before { content: "\f7ff"; }
+.bi-lungs::before { content: "\f800"; }
+.bi-microsoft-teams::before { content: "\f801"; }
+.bi-p-circle-1::before { content: "\f802"; }
+.bi-p-circle-fill-1::before { content: "\f803"; }
+.bi-p-circle-fill::before { content: "\f804"; }
+.bi-p-circle::before { content: "\f805"; }
+.bi-p-square-fill::before { content: "\f806"; }
+.bi-p-square::before { content: "\f807"; }
+.bi-pass-fill::before { content: "\f808"; }
+.bi-pass::before { content: "\f809"; }
+.bi-prescription::before { content: "\f80a"; }
+.bi-prescription2::before { content: "\f80b"; }
+.bi-r-circle-1::before { content: "\f80c"; }
+.bi-r-circle-fill-1::before { content: "\f80d"; }
+.bi-r-circle-fill::before { content: "\f80e"; }
+.bi-r-circle::before { content: "\f80f"; }
+.bi-r-square-fill::before { content: "\f810"; }
+.bi-r-square::before { content: "\f811"; }
+.bi-repeat-1::before { content: "\f812"; }
+.bi-repeat::before { content: "\f813"; }
+.bi-rewind-btn-fill::before { content: "\f814"; }
+.bi-rewind-btn::before { content: "\f815"; }
+.bi-rewind-circle-fill::before { content: "\f816"; }
+.bi-rewind-circle::before { content: "\f817"; }
+.bi-rewind-fill::before { content: "\f818"; }
+.bi-rewind::before { content: "\f819"; }
+.bi-train-freight-front-fill::before { content: "\f81a"; }
+.bi-train-freight-front::before { content: "\f81b"; }
+.bi-train-front-fill::before { content: "\f81c"; }
+.bi-train-front::before { content: "\f81d"; }
+.bi-train-lightrail-front-fill::before { content: "\f81e"; }
+.bi-train-lightrail-front::before { content: "\f81f"; }
+.bi-truck-front-fill::before { content: "\f820"; }
+.bi-truck-front::before { content: "\f821"; }
+.bi-ubuntu::before { content: "\f822"; }
+.bi-unindent::before { content: "\f823"; }
+.bi-unity::before { content: "\f824"; }
+.bi-universal-access-circle::before { content: "\f825"; }
+.bi-universal-access::before { content: "\f826"; }
+.bi-virus::before { content: "\f827"; }
+.bi-virus2::before { content: "\f828"; }
+.bi-wechat::before { content: "\f829"; }
+.bi-yelp::before { content: "\f82a"; }
+.bi-sign-stop-fill::before { content: "\f82b"; }
+.bi-sign-stop-lights-fill::before { content: "\f82c"; }
+.bi-sign-stop-lights::before { content: "\f82d"; }
+.bi-sign-stop::before { content: "\f82e"; }
+.bi-sign-turn-left-fill::before { content: "\f82f"; }
+.bi-sign-turn-left::before { content: "\f830"; }
+.bi-sign-turn-right-fill::before { content: "\f831"; }
+.bi-sign-turn-right::before { content: "\f832"; }
+.bi-sign-turn-slight-left-fill::before { content: "\f833"; }
+.bi-sign-turn-slight-left::before { content: "\f834"; }
+.bi-sign-turn-slight-right-fill::before { content: "\f835"; }
+.bi-sign-turn-slight-right::before { content: "\f836"; }
+.bi-sign-yield-fill::before { content: "\f837"; }
+.bi-sign-yield::before { content: "\f838"; }
+.bi-ev-station-fill::before { content: "\f839"; }
+.bi-ev-station::before { content: "\f83a"; }
+.bi-fuel-pump-diesel-fill::before { content: "\f83b"; }
+.bi-fuel-pump-diesel::before { content: "\f83c"; }
+.bi-fuel-pump-fill::before { content: "\f83d"; }
+.bi-fuel-pump::before { content: "\f83e"; }
+.bi-0-circle-fill::before { content: "\f83f"; }
+.bi-0-circle::before { content: "\f840"; }
+.bi-0-square-fill::before { content: "\f841"; }
+.bi-0-square::before { content: "\f842"; }
+.bi-rocket-fill::before { content: "\f843"; }
+.bi-rocket-takeoff-fill::before { content: "\f844"; }
+.bi-rocket-takeoff::before { content: "\f845"; }
+.bi-rocket::before { content: "\f846"; }
+.bi-stripe::before { content: "\f847"; }
+.bi-subscript::before { content: "\f848"; }
+.bi-superscript::before { content: "\f849"; }
+.bi-trello::before { content: "\f84a"; }
+.bi-envelope-at-fill::before { content: "\f84b"; }
+.bi-envelope-at::before { content: "\f84c"; }
+.bi-regex::before { content: "\f84d"; }
+.bi-text-wrap::before { content: "\f84e"; }
+.bi-sign-dead-end-fill::before { content: "\f84f"; }
+.bi-sign-dead-end::before { content: "\f850"; }
+.bi-sign-do-not-enter-fill::before { content: "\f851"; }
+.bi-sign-do-not-enter::before { content: "\f852"; }
+.bi-sign-intersection-fill::before { content: "\f853"; }
+.bi-sign-intersection-side-fill::before { content: "\f854"; }
+.bi-sign-intersection-side::before { content: "\f855"; }
+.bi-sign-intersection-t-fill::before { content: "\f856"; }
+.bi-sign-intersection-t::before { content: "\f857"; }
+.bi-sign-intersection-y-fill::before { content: "\f858"; }
+.bi-sign-intersection-y::before { content: "\f859"; }
+.bi-sign-intersection::before { content: "\f85a"; }
+.bi-sign-merge-left-fill::before { content: "\f85b"; }
+.bi-sign-merge-left::before { content: "\f85c"; }
+.bi-sign-merge-right-fill::before { content: "\f85d"; }
+.bi-sign-merge-right::before { content: "\f85e"; }
+.bi-sign-no-left-turn-fill::before { content: "\f85f"; }
+.bi-sign-no-left-turn::before { content: "\f860"; }
+.bi-sign-no-parking-fill::before { content: "\f861"; }
+.bi-sign-no-parking::before { content: "\f862"; }
+.bi-sign-no-right-turn-fill::before { content: "\f863"; }
+.bi-sign-no-right-turn::before { content: "\f864"; }
+.bi-sign-railroad-fill::before { content: "\f865"; }
+.bi-sign-railroad::before { content: "\f866"; }
+.bi-building-add::before { content: "\f867"; }
+.bi-building-check::before { content: "\f868"; }
+.bi-building-dash::before { content: "\f869"; }
+.bi-building-down::before { content: "\f86a"; }
+.bi-building-exclamation::before { content: "\f86b"; }
+.bi-building-fill-add::before { content: "\f86c"; }
+.bi-building-fill-check::before { content: "\f86d"; }
+.bi-building-fill-dash::before { content: "\f86e"; }
+.bi-building-fill-down::before { content: "\f86f"; }
+.bi-building-fill-exclamation::before { content: "\f870"; }
+.bi-building-fill-gear::before { content: "\f871"; }
+.bi-building-fill-lock::before { content: "\f872"; }
+.bi-building-fill-slash::before { content: "\f873"; }
+.bi-building-fill-up::before { content: "\f874"; }
+.bi-building-fill-x::before { content: "\f875"; }
+.bi-building-fill::before { content: "\f876"; }
+.bi-building-gear::before { content: "\f877"; }
+.bi-building-lock::before { content: "\f878"; }
+.bi-building-slash::before { content: "\f879"; }
+.bi-building-up::before { content: "\f87a"; }
+.bi-building-x::before { content: "\f87b"; }
+.bi-buildings-fill::before { content: "\f87c"; }
+.bi-buildings::before { content: "\f87d"; }
+.bi-bus-front-fill::before { content: "\f87e"; }
+.bi-bus-front::before { content: "\f87f"; }
+.bi-ev-front-fill::before { content: "\f880"; }
+.bi-ev-front::before { content: "\f881"; }
+.bi-globe-americas::before { content: "\f882"; }
+.bi-globe-asia-australia::before { content: "\f883"; }
+.bi-globe-central-south-asia::before { content: "\f884"; }
+.bi-globe-europe-africa::before { content: "\f885"; }
+.bi-house-add-fill::before { content: "\f886"; }
+.bi-house-add::before { content: "\f887"; }
+.bi-house-check-fill::before { content: "\f888"; }
+.bi-house-check::before { content: "\f889"; }
+.bi-house-dash-fill::before { content: "\f88a"; }
+.bi-house-dash::before { content: "\f88b"; }
+.bi-house-down-fill::before { content: "\f88c"; }
+.bi-house-down::before { content: "\f88d"; }
+.bi-house-exclamation-fill::before { content: "\f88e"; }
+.bi-house-exclamation::before { content: "\f88f"; }
+.bi-house-gear-fill::before { content: "\f890"; }
+.bi-house-gear::before { content: "\f891"; }
+.bi-house-lock-fill::before { content: "\f892"; }
+.bi-house-lock::before { content: "\f893"; }
+.bi-house-slash-fill::before { content: "\f894"; }
+.bi-house-slash::before { content: "\f895"; }
+.bi-house-up-fill::before { content: "\f896"; }
+.bi-house-up::before { content: "\f897"; }
+.bi-house-x-fill::before { content: "\f898"; }
+.bi-house-x::before { content: "\f899"; }
+.bi-person-add::before { content: "\f89a"; }
+.bi-person-down::before { content: "\f89b"; }
+.bi-person-exclamation::before { content: "\f89c"; }
+.bi-person-fill-add::before { content: "\f89d"; }
+.bi-person-fill-check::before { content: "\f89e"; }
+.bi-person-fill-dash::before { content: "\f89f"; }
+.bi-person-fill-down::before { content: "\f8a0"; }
+.bi-person-fill-exclamation::before { content: "\f8a1"; }
+.bi-person-fill-gear::before { content: "\f8a2"; }
+.bi-person-fill-lock::before { content: "\f8a3"; }
+.bi-person-fill-slash::before { content: "\f8a4"; }
+.bi-person-fill-up::before { content: "\f8a5"; }
+.bi-person-fill-x::before { content: "\f8a6"; }
+.bi-person-gear::before { content: "\f8a7"; }
+.bi-person-lock::before { content: "\f8a8"; }
+.bi-person-slash::before { content: "\f8a9"; }
+.bi-person-up::before { content: "\f8aa"; }
+.bi-scooter::before { content: "\f8ab"; }
+.bi-taxi-front-fill::before { content: "\f8ac"; }
+.bi-taxi-front::before { content: "\f8ad"; }
+.bi-amd::before { content: "\f8ae"; }
+.bi-database-add::before { content: "\f8af"; }
+.bi-database-check::before { content: "\f8b0"; }
+.bi-database-dash::before { content: "\f8b1"; }
+.bi-database-down::before { content: "\f8b2"; }
+.bi-database-exclamation::before { content: "\f8b3"; }
+.bi-database-fill-add::before { content: "\f8b4"; }
+.bi-database-fill-check::before { content: "\f8b5"; }
+.bi-database-fill-dash::before { content: "\f8b6"; }
+.bi-database-fill-down::before { content: "\f8b7"; }
+.bi-database-fill-exclamation::before { content: "\f8b8"; }
+.bi-database-fill-gear::before { content: "\f8b9"; }
+.bi-database-fill-lock::before { content: "\f8ba"; }
+.bi-database-fill-slash::before { content: "\f8bb"; }
+.bi-database-fill-up::before { content: "\f8bc"; }
+.bi-database-fill-x::before { content: "\f8bd"; }
+.bi-database-fill::before { content: "\f8be"; }
+.bi-database-gear::before { content: "\f8bf"; }
+.bi-database-lock::before { content: "\f8c0"; }
+.bi-database-slash::before { content: "\f8c1"; }
+.bi-database-up::before { content: "\f8c2"; }
+.bi-database-x::before { content: "\f8c3"; }
+.bi-database::before { content: "\f8c4"; }
+.bi-houses-fill::before { content: "\f8c5"; }
+.bi-houses::before { content: "\f8c6"; }
+.bi-nvidia::before { content: "\f8c7"; }
+.bi-person-vcard-fill::before { content: "\f8c8"; }
+.bi-person-vcard::before { content: "\f8c9"; }
+.bi-sina-weibo::before { content: "\f8ca"; }
+.bi-tencent-qq::before { content: "\f8cb"; }
+.bi-wikipedia::before { content: "\f8cc"; }

BIN
static/bootstrap-icons.woff2


Fișier diff suprimat deoarece este prea mare
+ 5 - 0
static/bootstrap.bundle.min.js


Fișier diff suprimat deoarece este prea mare
+ 4 - 0
static/bootstrap.min.css


BIN
static/img/brand/ispoogedaily_logo.jpg


+ 16 - 0
static/theme/base-bs.css

@@ -0,0 +1,16 @@
+
+.qt-box, .card-box {
+	padding: 4px;
+	border: 1px solid black;
+}
+
+
+#tweets .tweet.marked {
+	background-color: powderblue;
+}
+
+
+
+.theme a:visited {
+	color: orange;
+}

+ 2 - 2
static/tweets-ui.js

@@ -157,13 +157,13 @@ function copyToClipboard (text) {
 }
 }
 
 
 function tweetById (tweetId) {
 function tweetById (tweetId) {
-	var tweets = window.dataset.items.filter(t => t.id == tweetId);
+	var tweets = window.dataset.get().filter(t => t.feed_item.id == tweetId);
 	
 	
 	if (!tweets.length) {
 	if (!tweets.length) {
 		return null;
 		return null;
 	}
 	}
 	
 	
-	return tweets[0];
+	return tweets[0].feed_item;
 }
 }
 
 
 
 

+ 111 - 0
templates/base-bs.html

@@ -0,0 +1,111 @@
+<!DOCTYPE html>
+<html lang="en" data-bs-theme="dark">
+<head>
+	<meta charset="utf-8">
+	<meta name="viewport" content="width=device-width, initial-scale=1">
+
+	
+    <link href="{{ url_for('static', filename='bootstrap.min.css') }}" rel="stylesheet" integrity="sha384-GLhlTQ8iRABdZLl6O3oVMWSktQOp6b7In1Zl3/Jr59b6EGGoI1aFkw7cmDA6j6gD" crossorigin="anonymous">
+	<script src="{{ url_for('static', filename='bootstrap.bundle.min.js') }}" integrity="sha384-w76AqPfDkMBDXo30jS1Sgez6pr3x5MlQ1ZAGC+nuZB+EYdgRZgiwxhTBTkF7CXvN" crossorigin="anonymous"></script>
+	
+	<link rel="stylesheet" href="{{ url_for('static', filename='bootstrap-icons.css') }}">
+
+	<link rel="stylesheet" href="{{ url_for('static', filename='theme/base-bs.css') }}">
+
+	{% if visjs_enabled %}
+	
+	<script src="{{ url_for('visjs.static', filename='vis-timeline-graph2d.min.js') }}"></script>
+	<link href="{{ url_for('visjs.static', filename='vis-timeline-graph2d.min.css') }}" rel="stylesheet" type="text/css" />
+	
+	{% endif %}
+
+	<script src="{{ url_for('static', filename='htmx.js') }}"></script>
+
+	{% block head %}
+	<title>{{ title | default('No Title') }}</title>
+	{% endblock %}
+	
+
+	
+	{% if opengraph_info %}
+	{% for prop, content in opengraph_info.items() %}
+	<meta property="og:{{prop}}" content="{{content}}" />
+	{% endfor %}
+	{% endif %}
+	
+<script>
+const Toast = Swal.mixin({
+  toast: true,
+  position: 'top-end',
+  showConfirmButton: false,
+  timer: 3000,
+  timerProgressBar: true,
+  didOpen: (toast) => {
+    toast.addEventListener('mouseenter', Swal.stopTimer)
+    toast.addEventListener('mouseleave', Swal.resumeTimer)
+  }
+})
+</script>
+
+</head>
+<body>
+
+<div class="container">
+
+<div class="row">
+
+<div class="col-lg-3 fixed-lg p-lg-4">
+<h1>Hogumathi</h1>
+
+<ul>
+	{% if nav_items %}
+		{% for nav_item in nav_items|sort(attribute='order') %}
+		<li><a href="{{ nav_item.href }}">{{ nav_item.label }}</a></li>
+		{% endfor %}
+	{% endif %}
+	
+	<!--
+	<li><a href="javascript:document.body.classList.toggle('theme-dark')">Toggle Dark Mode</a></li>
+	<li><a href="javascript:document.location.reload()">Refresh</a></li>
+	-->
+</ul>
+
+
+{% if twitter_user or mastodon_user %}
+
+{% include "partial/compose-form.html" %}
+
+{% endif %}
+
+
+{% if False %}
+<!--
+{% include "partial/media-upload-form.html" %}
+-->
+{% endif %}
+
+
+
+<h2>Accounts</h2>
+
+{% include "partial/user-picker.html" %}
+
+{% if add_account_enabled %}
+<a href="{{ url_for('get_login_html') }}">Add account</a>
+{% endif %}
+
+
+</div>
+
+<div class="col-lg-9">
+
+{% block content %}{% endblock %}
+
+</div>
+
+</div>
+
+</div>
+
+</body>
+</html>

+ 10 - 3
templates/brand-page.html

@@ -1,4 +1,4 @@
-{% extends "base.html" %}
+{% extends "base-bs.html" %}
 
 
 {% block head %}
 {% block head %}
 	<title>{{ brand.display_name }} on Hogumathi</title>
 	<title>{{ brand.display_name }} on Hogumathi</title>
@@ -7,11 +7,18 @@
 
 
 {% block content %}
 {% block content %}
 	<div class="w-100" style="height: 80px;">
 	<div class="w-100" style="height: 80px;">
-		{% include "partial/page-nav.html" %}
+		{% include "partial/page-nav-bs.html" %}
 	</div>
 	</div>
 	
 	
+	<nav class="nav nav-pills flex-column flex-sm-row">
+		<a class="flex-sm-fill text-sm-center nav-link active" aria-current="page" href="#">Products &amp; Socials</a>
+		<a class="flex-sm-fill text-sm-center nav-link" href="#">Feeds</a>
+		<a class="flex-sm-fill text-sm-center nav-link" href="#">Partners</a>
+		<a class="flex-sm-fill text-sm-center nav-link disabled">Analytics</a>
+	</nav>
+	
 	{% if brand %}
 	{% if brand %}
-	{% include "partial/brand-info.html" %}
+	{% include "partial/brand-info-bs.html" %}
 	{% endif %}
 	{% endif %}
 	
 	
 {% endblock %}
 {% endblock %}

+ 12 - 0
templates/followers.html

@@ -16,6 +16,18 @@
 
 
 </ul>
 </ul>
 
 
+<h2>Timeline activity
+
+<ul>
+
+<li>Likes, replies, retweets
+
+<li>Impressions, prof views, clicks, link clicks
+
+<li>Follower vs. Non
+
+</ul>
+
 </div>
 </div>
 {% endif %}
 {% endif %}
 
 

+ 367 - 0
templates/partial/brand-info-bs.html

@@ -0,0 +1,367 @@
+<div class="container-fluid w-100 ">
+
+<style>
+
+.brand-info ul {
+	list-style: none;
+	padding-left: 0;
+}
+
+.brand-info ul li {
+	padding-left: 0;
+}
+
+.brand-info ul li a.btn {
+	text-align: left;
+}
+
+.brand-info ul li .form-control {
+	overflow-wrap: break-word;
+}
+
+@media (min-width: 992px) {
+	.ms-lg-35 {
+		margin-left: 35% !important;
+	}
+}
+
+
+
+</style>
+
+<div class="brand-info w-100 p-2 bg-body border-black">
+
+<div class="bg-body border-bottom border-dark sticky-lg-top mb-5 mb-lg-0" style="z-index: 9999">
+{% if 'logo_href' in brand %}
+
+<center>
+<img class="m-4 w-25 w-lg-50" src="{{ brand.logo_href }}" style="float: left">
+
+</center>
+
+{% endif %}
+
+
+<h1>{{ brand.display_name }}</h1>
+
+
+
+{% if brand.description %}
+{% for para in brand.description.split('\n') %}
+<p>{{ para }}</p>
+{% endfor %}
+{% endif %}
+
+</div>
+
+<div class="ms-0 ms-lg-35">
+{% if gumroad and gumroad.products %}
+
+	<h2>Products</h2>
+	
+	
+	<div class="row row-cols-2 row-cols-lg-3 g-2 mb-3 mt-1">
+		{% for product in gumroad.products %}
+		<div class="col-sm">
+			<div class="card">
+				<img src="{{ product.cover_image_uri }}" class="card-img-top">
+				<h5 class="card-title">{{ product.name }}</h5>
+				<p class="card-text">({{ product.price }})</p>
+				
+				<a href="{{ product.href }}" class="btn btn-primary">
+					<i class="bi-box2-heart"></i>
+					On Gumroad
+				</a>
+				
+			</div>
+		</div>
+		{% endfor %}
+	</div>
+
+	
+
+
+{% endif %}
+
+
+
+
+{% if patreon or (gumroad and gumroad.subscriptions and len(gumroad.subscriptions)) %}
+	<h2>Subscriptions</h2>
+
+	<ul>
+
+	{% if patreon %}
+	<li>
+		<div class="input-group mb-1" style="width: fit-content">
+			<a class="btn btn-primary" href="{{ patreon.href }}">
+				<i class="bi-calendar-heart"></i>
+				Patreon
+			</a>
+			<span class="form-control px-4" >
+				@{{ patreon.id }}
+			</span>
+		</div>
+	</li>
+	{% endif %}
+
+	{% for sub in gumroad.subscriptions %}
+	{% if sub.tiers  %}
+	<li>
+		<h4>{{ sub.name }} on Gumroad</h4>
+		<ul>
+		{% for tier in sub.tiers %}
+		<li>
+			
+			<div class="input-group mb-1" style="width: fit-content">
+				<a class="btn btn-primary" href="{{ sub.href }}?option={{ tier.option }}">
+					<i class="bi-calendar-heart"></i>
+					{{ tier.name }}
+				</a>
+				<span class="form-control px-4" >
+					{{ tier.yearly }}/yr or {{ tier.monthly }}/mo
+				</span>
+			</div>
+		
+			<!--
+			<a href="{{ sub.href }}?option={{ tier.option }}">
+			{{ tier.name }}
+			</a>
+			({{ tier.yearly }}/yr or {{ tier.monthly }}/mo)
+			-->
+		</li>
+		{% endfor %}
+		</ul>
+	</li>
+	{% else %}
+	<li>
+		
+		<div class="input-group mb-1" style="width: fit-content">
+			<a class="btn btn-primary" href="{{ sub.href }}">
+				<i class="bi-calendar-heart"></i>
+				{{ sub.name }}
+			</a>
+			<span class="form-control px-4" >
+				{{ sub.yearly }}/yr or {{ sub.monthly }}/mo
+			</span>
+		</div>
+	</li>
+	{% endif %}
+	{% endfor %}
+
+	</ul>
+{% endif %}
+
+
+{% if venmo %}
+<h2>Tips</h2>
+
+<ul>
+
+{% if venmo %}
+<li>
+	<div class="input-group" style="width: fit-content">
+		<a class="btn btn-primary" href="{{ venmo.href }}">
+			<i class="bi-heart"></i>
+			Venmo
+		</a>
+		<span class="form-control" >
+			@{{ venmo.id }}
+		</span>
+	</div>
+</li>
+{% endif %}
+
+</ul>
+{% endif %}
+
+
+{% if twitter or youtube or mastodon or receipt_tracker %}
+	<h2>On Hogumathi</h2>
+
+	<ul>
+
+	{% if twitter %}
+	<li>
+		<div class="input-group mb-1" style="width: fit-content">
+		
+			<a href="{{ twitter.hogu_href }}" class="btn btn-primary">
+				<i class="bi-twitter"></i>
+				<span class="d-none d-md-inline">
+					Twitter
+				</span>
+			</a>
+			<span class="form-control">
+				@{{ twitter.username }}
+				
+			</span>
+		</div>
+		<!--
+		<small class="d-none d-md-inline">
+			(<a class="text-decoration-none" href="{{ twitter.href }}">on Twitter.com</a>)
+		</small>
+		[<a href="{{ twitter.href }}">source</a>]
+		-->
+	</li>
+	{% endif %}
+
+	{% if youtube %}
+	<li>
+		<div class="input-group mb-1" style="width: fit-content">
+		
+			<a href="{{ youtube.hogu_href }}" class="btn btn-primary">
+				<i class="bi-youtube"></i>
+				<span class="d-none d-md-inline">
+					YouTube
+				</span>
+			</a>
+			<span class="form-control">
+				@{{ youtube.username }}
+			</span>
+		</div>
+	
+		<!--
+		<a href="{{ youtube.hogu_href }}">YouTube</a> (@{{ youtube.username }}) [<a href="{{ youtube.href }}">source</a>]
+		-->
+	</li>
+	{% endif %}
+
+	{% if mastodon %}
+	<li>
+		<div class="input-group mb-1" style="width: fit-content">
+		
+			<a href="{{ mastodon.hogu_href }}" class="btn btn-primary">
+				<i class="bi-mastodon"></i>
+				<span class="d-none d-md-inline">
+					Mastodon
+				</span>
+			</a>
+			<span class="form-control">
+				@{{ mastodon.username }}@{{ mastodon.instance }}
+			</span>
+		</div>
+	
+		<!--
+		<a href="{{ mastodon.hogu_href }}">Mastodon</a> (@{{ mastodon.username }}@{{ mastodon.instance }}) [<a href="{{ mastodon.href }}">source</a>]
+		-->
+	</li>
+	{% endif %}
+
+	{% if receipt_tracker %}
+	<li>
+		<div class="input-group mb-1" style="width: fit-content">
+		
+			<a href="{{ receipt_tracker.hogu_href }}" class="btn btn-primary">
+				<i class="bi-rss"></i>
+				<span class="d-none d-md-inline">
+					RSS
+				</span>
+			</a>
+			<span class="form-control">
+				{{ receipt_tracker.href }}
+			</span>
+		</div>
+		<!--
+		<a href="{{ receipt_tracker.hogu_href }}">Receipts</a> [<a href="{{ receipt_tracker.href }}">source</a>]
+		-->
+	</li>
+	{% endif %}
+
+	</ul>
+{% endif %}
+
+{% if mailchimp or discord %}
+	<h2>Community</h2>
+
+	<ul>
+
+	{% if mailchimp %}
+	<li><a href="{{ mailchimp.href }}">Mailchimp</a></li>
+	{% endif %}
+
+	{% if discord %}
+	<li><a href="{{ discord.href }}">Discord</a></li>
+	{% endif %}
+
+	</ul>
+{% endif %}
+
+{% if twitch or instagram %}
+	<h2>Other Socials</h2>
+
+	<ul>
+
+	{% if twitch %}
+	<li>
+		<div class="input-group mb-1" style="width: fit-content">
+		
+			<a href="{{ twitch.hogu_href }}" class="btn btn-primary">
+				<i class="bi-twitch"></i>
+				<span class="d-none d-md-inline">
+					Twitch
+				</span>
+			</a>
+			<span class="form-control">
+				@{{ twitch.id }}
+			</span>
+		</div>
+		<!--
+		<a href="{{ twitch.href }}">Twitch</a> (@{{ twitch.id }})</a>
+		-->
+	</li>
+	{% endif %}
+
+
+	{% if instagram %}
+	<li>
+		<div class="input-group mb-1" style="width: fit-content">
+		
+			<a href="{{ instagram.href }}" class="btn btn-primary">
+				<i class="bi-instagram"></i>
+				<span class="d-none d-md-inline">
+					Instagram
+				</span>
+			</a>
+			<span class="form-control">
+				@{{ instagram.id }}
+			</span>
+		</div>
+		<!--
+		<a href="{{ instagram.href }}">Instagram</a> (@{{ instagram.id }})</a>
+		-->
+	</li>
+	{% endif %}
+	</ul>
+{% endif %}
+
+
+
+{% if contributors %}
+
+	<h2>Contributors</h2>
+	
+	<p>Thanks to our kind contributors for helping support development:</p>
+	
+	<table class="table table-striped">
+		<thead>
+		<tr>
+			<th>Name</th>
+			<th>Amount (Est)</th>
+		</tr>
+		</thead>
+		<tbody>
+		{% for contributor in contributors %}
+			<tr>
+				<td>{{ contributor.name }}</td>
+				<td>{{ contributor.amount }}</td>
+			</tr>
+		{% endfor %}
+		</tbody>
+	</table>
+
+{% endif %}
+
+</div>
+
+</div>
+</div>

+ 66 - 6
templates/partial/brand-info.html

@@ -2,6 +2,19 @@
 
 
 <h1>{{ brand.display_name }}</h1>
 <h1>{{ brand.display_name }}</h1>
 
 
+{% if 'logo_href' in brand %}
+
+<img src="{{ brand.logo_href }}" style="max-width: 25%">
+
+{% endif %}
+
+{% if brand.description %}
+{% for para in brand.description.split('\n') %}
+<p>{{ para }}</p>
+{% endfor %}
+{% endif %}
+
+{% if venmo %}
 <h2>Tips</h2>
 <h2>Tips</h2>
 
 
 <ul>
 <ul>
@@ -11,11 +24,11 @@
 {% endif %}
 {% endif %}
 
 
 </ul>
 </ul>
+{% endif %}
 
 
 
 
 
 
-
-{% if patreon or gumroad %}
+{% if patreon or (gumroad and gumroad.subscriptions and len(gumroad.subscriptions)) %}
 	<h2>Subscriptions</h2>
 	<h2>Subscriptions</h2>
 
 
 	<ul>
 	<ul>
@@ -24,13 +37,35 @@
 	<li><a href="{{ patreon.href }}">Patreon</a> (@{{ patreon.id }})</li>
 	<li><a href="{{ patreon.href }}">Patreon</a> (@{{ patreon.id }})</li>
 	{% endif %}
 	{% endif %}
 
 
-	{% if gumroad %}
-	<li><a href="{{ gumroad.href }}">Gumroad</a></li>
+	{% for sub in gumroad.subscriptions %}
+	{% if sub.tiers  %}
+	<li>{{ sub.name }}
+		<ul>
+		{% for tier in sub.tiers %}
+		<li><a href="{{ sub.href }}?option={{ tier.option }}">{{ tier.name }}</a> ({{ tier.yearly }}/yr or {{ tier.monthly }}/mo)</li>
+		{% endfor %}
+		</ul>
+	</li>
+	{% else %}
+	<li><a href="{{ sub.href }}">{{ sub.name }} ({{ sub.yearly }}/yr or {{ sub.monthly }}/mo)</a></li>
 	{% endif %}
 	{% endif %}
+	{% endfor %}
 
 
 	</ul>
 	</ul>
 {% endif %}
 {% endif %}
 
 
+{% if gumroad and gumroad.products %}
+
+	<h2>Products</h2>
+	
+	<ul>
+	{% for product in gumroad.products %}
+	<li><a href="{{ product.href }}">{{ product.name }}</a> ({{ product.price }})</li>
+	
+	{% endfor %}
+	</ul>
+
+{% endif %}
 
 
 {% if twitter or youtube or mastodon or receipt_tracker %}
 {% if twitter or youtube or mastodon or receipt_tracker %}
 	<h2>On Hogumathi</h2>
 	<h2>On Hogumathi</h2>
@@ -85,9 +120,34 @@
 	{% if instagram %}
 	{% if instagram %}
 	<li><a href="{{ instagram.href }}">Instagram</a> (@{{ instagram.id }})</a></li>
 	<li><a href="{{ instagram.href }}">Instagram</a> (@{{ instagram.id }})</a></li>
 	{% endif %}
 	{% endif %}
-
+	</ul>
 {% endif %}
 {% endif %}
 
 
-</ul>
+
+
+{% if contributors %}
+
+	<h2>Contributors</h2>
+	
+	<p>Thanks to our kind contributors for helping support development:</p>
+	
+	<table>
+		<thead>
+		<tr>
+			<th>Name</th>
+			<th>Amount (Est)</th>
+		</tr>
+		</thead>
+		<tbody>
+		{% for contributor in contributors %}
+			<tr>
+				<td>{{ contributor.name }}</td>
+				<td>{{ contributor.amount }}</td>
+			</tr>
+		{% endfor %}
+		</tbody>
+	</table>
+
+{% endif %}
 
 
 </div>
 </div>

+ 10 - 0
templates/partial/page-nav-bs.html

@@ -0,0 +1,10 @@
+<div class="d-flex flex-row mt-1 flex-wrap">
+	
+		{% if page_nav %}
+		{% for nav_item in page_nav|sort(attribute='order') %}
+		<a class="btn btn-secondary btn-sm mx-1" href="{{ nav_item.href }}">{{ nav_item.label }}</a>
+		{% endfor %}
+		{% endif %}
+		
+	
+</div>

+ 204 - 0
templates/partial/timeline-tweet-bs.html

@@ -0,0 +1,204 @@
+
+<div style="width: 60px; max-width: 60px; min-width: 60px">
+	<img loading="lazy"  src="{{ tweet.avi_icon_url }}" alt="Avi">
+</div>
+<div class="d-flex flex-column flex-grow-1">
+	<p class="mt-0 pt-0">
+	<strong><a href="{{ tweet.author_url }}" class="w-100">{{ tweet.display_name }}</a></strong>
+	{% if tweet.author_is_verified %}
+	<small class="verified">[verified]</small>
+	{% endif %}
+	
+	<a href="{{ tweet.author_url }}" class="silver">@{{ tweet.handle }}</a>
+	<a href="{{ tweet.url }}">{{ tweet.created_at }}</a> [<a href="{{ tweet.source_url }}" target="tweet_{{ tweet.id }}">source</a>]
+	</p>
+	
+	<p>
+	
+	{% if tweet.html %}
+		{{ tweet.html | safe }}
+	{% else %}
+		{{ tweet.text | replace('<', '&lt;') | replace('\n', '<br>') | safe }}
+	{% endif %}
+	</p>
+
+	{% if tweet.quoted_tweet %}
+	<div class="d-flex flex-row">
+		{% with tweet = tweet.quoted_tweet %}
+			{% include "partial/timeline-tweet-bs.html" %}
+		{% endwith %}
+	</div>
+	{% endif %}
+	
+	{% if not skip_embed_replies %}
+	{% if tweet.replied_tweet %}
+	<p style="color: silver">
+	Replying to:
+	</p>
+	<div class="reply_to" style="border: 1px solid silver; padding: 6px">
+		<div class="d-flex flex-row reply-box">
+			{% with tweet = tweet.replied_tweet %}
+				{% include "partial/timeline-tweet-bs.html" %}
+			{% endwith %}
+
+		</div>
+	</div>
+	{% elif tweet.replied_tweet_id %}
+	<p style="color: silver">
+	Replying to:
+	</p>
+	{% if tweet.actions.view_replied_tweet %}
+	<p class="reply_to w-100" style="border: 1px solid silver; padding: 6px">
+		<a href="{{ url_for(tweet.actions.view_replied_tweet.route, **tweet.actions.view_replies.route_params) }}">View in Thread</a>.
+	</p>
+	{% endif %}
+	{% endif %}
+	{% endif %}
+	
+	{% if tweet.note %}
+	<p class="note" style="border: 1px solid black; background-color: yellow; padding: 6px">
+		{{ tweet.note.replace('\n', '<br>') | safe }}
+	</p>
+	{% endif %}
+
+	{% if tweet.photos %}
+	
+		<ul>
+		{% for photo in tweet.photos %}
+			<li><img loading="lazy" class="w-100" src="{{ photo.preview_image_url }}" crossorigin="" referrerpolicy="no-referrer" onclick="this.src='{{ photo.url }}'"></li>
+		{% endfor %}
+		</ul>
+
+
+	{% endif %}
+
+	{% if tweet.videos %}
+
+		<ul>
+		{% for video in tweet.videos %}
+			<li><img loading="lazy" class="w-100" 
+			       src="{{ video.preview_image_url }}" referrerpolicy="no-referrer"
+				   
+				   
+				   {% if video.image_url %}
+				   onclick="this.src='{{ video.image_url }}'; this.onclick = undefined" 
+				   {% endif %}
+				   
+				   {% if video.url %}
+				   ondblclick="swapVideoPlayer(this, '{{ video.url }}', '{{ video.content_type }}')"
+				   {% endif %}
+				   >
+				   
+				   <dl>
+				    {% if video.duration_str  %}
+					<dt>Duration</dt>
+					<dd>{{ video.duration_str }}</dt>
+					{% elif video.duration_ms  %}
+					<dt>Duration</dt>
+					<dd>{{ video.duration_ms / 1000 / 60 }} minutes</dt>
+					{% endif %}
+				   </dl>
+				   
+				   {% if video.public_metrics and video.public_metrics.view_count %}
+				   <p class="w-100">
+						view count: {{ video.public_metrics.view_count }}
+				   </p>
+				   {% endif %}
+				   
+				   </li>
+				   
+		{% endfor %}
+		</ul>
+
+	{% endif %}
+	
+		{% if tweet.card %}
+		
+
+		<div class="card-box">
+		
+			<p><a href="{{ tweet.card.source_url }}">{{ tweet.card.display_url }}</a></p>
+			<p><strong>{{ tweet.card.title }}</strong></p>
+			<p>{{ tweet.card.content }}</p>
+		</div>
+		{% endif %}
+		
+		
+				
+
+		{% if False and tweet.replied_tweet %}
+			<a href="{{ tweet.replied_tweet.url }}">View Parent</a>
+			<a href="{{ url_for('.get_tweet_html', tweet_id=tweet.replied_tweet.conversation_id) }}">View Conversation</a>
+		{% endif %}
+		
+		{% if tweet.public_metrics %}
+		
+
+		<p>
+		{% for k, v in tweet.public_metrics.items() %}
+			{% if v != None %}
+			{{ k.replace('_count', 's').replace('ys', 'ies').replace('_', ' ') }}: {{ v }}, 
+			{% endif %}
+		{% endfor %}
+		
+		</p>
+		{% endif %}
+		
+		
+		
+		{% if tweet.non_public_metrics %}
+		
+
+		
+		<p>
+			{% for k, v in tweet.non_public_metrics.items() %}
+				{% if v != None %}
+				{{ k.replace('_count', 's').replace('ys', 'ies').replace('_', ' ') }}: {{ v }},
+				{% endif %}				
+			{% endfor %}
+			
+		</p>
+		{% endif %}
+		
+		{% if tweet.attachments %}
+		<ul>
+		{% for a in tweet.attachments %}
+			{% if a.content_type == 'application/vnd-hogumathi.livestream-details+json' %}
+			<li class="livestream-details">
+				<dl>
+					
+					{% if a.content.scheduled_start_time %}
+					<dt>Scheduled Start Time</dt>
+					<dd>{{ a.content.scheduled_start_time }}</dd>
+					{% endif %}
+					
+					{% if a.content.start_time %}
+					<dt>Start Time</dt>
+					<dd>{{ a.content.start_time }}</dd>
+					{% endif %}
+					
+					{% if a.content.chat_embed_url %}
+					<dt>Chat</dt>
+					<dd>
+						<iframe class="w-100" height="400" src="{{ a.content.chat_embed_url }}" referrerpolicy="origin"></iframe>
+					</dd>
+					{% endif %}
+					
+					
+				</dl>
+			</li>
+			{% else %}
+			<li><a href="{{ a.url }}">{{ a.name }}</a> {{ a.content_type }} ({{ a.size }})
+			{% endif %}
+			</li>
+		{% endfor %}
+		</ul>
+		{% endif %}
+	
+		{% if show_conversation_id %}
+		<p>
+			Conversation: {{ tweet.conversation_id }}
+			
+		</p>
+		{% endif %}
+</div>

+ 3 - 3
templates/partial/timeline-tweet.html

@@ -1,6 +1,6 @@
 
 
 <div class="dtc w-10">
 <div class="dtc w-10">
-	<img src="{{ tweet.avi_icon_url }}" alt="Avi">
+	<img loading="lazy"  src="{{ tweet.avi_icon_url }}" alt="Avi">
 </div>
 </div>
 <div class="dtc w-90 v-top">
 <div class="dtc w-90 v-top">
 	<p class="w-100 mt0 pt0">
 	<p class="w-100 mt0 pt0">
@@ -69,7 +69,7 @@
 	<p class="w-100">
 	<p class="w-100">
 		<ul>
 		<ul>
 		{% for photo in tweet.photos %}
 		{% for photo in tweet.photos %}
-			<li><img class="w-100" src="{{ photo.preview_image_url }}" crossorigin="" referrerpolicy="no-referrer" onclick="this.src='{{ photo.url }}'"></li>
+			<li><img loading="lazy" class="w-100" src="{{ photo.preview_image_url }}" crossorigin="" referrerpolicy="no-referrer" onclick="this.src='{{ photo.url }}'"></li>
 		{% endfor %}
 		{% endfor %}
 		</ul>
 		</ul>
 
 
@@ -81,7 +81,7 @@
 		<p>VIDEOS</p>
 		<p>VIDEOS</p>
 		<ul>
 		<ul>
 		{% for video in tweet.videos %}
 		{% for video in tweet.videos %}
-			<li><img class="w-100" 
+			<li><img loading="lazy" class="w-100" 
 			       src="{{ video.preview_image_url }}" referrerpolicy="no-referrer"
 			       src="{{ video.preview_image_url }}" referrerpolicy="no-referrer"
 				   
 				   
 				   
 				   

+ 360 - 0
templates/partial/tweets-timeline-bs.html

@@ -0,0 +1,360 @@
+
+
+<script src="{{ url_for('static', filename='tweets-ui.js') }}"></script>
+
+
+<script>
+
+{% if notes_app_url %}
+var notesAppUrl = {{ notes_app_url | tojson }}
+{% endif %}
+
+	if (!window['dataset']) {
+		{% if visjs_enabled %}
+		window.dataset = new vis.DataSet();
+		{% else %}
+		window.dataset = {
+			items: [],
+			update: function (items) {
+				dataset.items = dataset.items.concat(items);
+			},
+			get: function () {
+				return items;
+			}
+		}
+		{% endif %}
+	}
+</script>
+
+<script>
+function feed_item_to_activity (fi) {
+		
+		var group = 'tweet';
+		var y = 1;
+		if (fi.retweeted_tweet_id) {
+			group = 'retweet';
+			y = 2;
+		} else if (fi.replied_tweet_id) {
+			group = 'reply';
+			y = 3;
+		}
+		
+		return {
+			//'id': fi.id,
+			'x': new Date(fi.created_at),
+			'y': y,
+			'group': group,
+			'feed_item': fi
+		}
+	}
+	
+	function feed_item_to_likes (fi) {
+		
+		if ( !fi['public_metrics'] || !fi.public_metrics['like_count'] ) {
+			return;
+		}
+		
+		var group = 'likes';
+		var y = fi.public_metrics.like_count;
+		
+		
+		return {
+			//'id': fi.id,
+			'x': new Date(fi.created_at),
+			'y': y,
+			'group': group,
+			'feed_item': fi
+		}
+	}
+	
+	function feed_item_to_replies (fi) {
+		
+		if ( !fi['public_metrics'] || !fi.public_metrics['reply_count'] ) {
+			return;
+		}
+		
+		var group = 'replies';
+		var y = fi.public_metrics.reply_count;
+		
+		
+		return {
+			//'id': fi.id,
+			'x': new Date(fi.created_at),
+			'y': y,
+			'group': group,
+			'feed_item': fi
+		}
+	}
+</script>
+
+{% if twitter_live_enabled and visjs_enabled and not skip_plot %}
+
+<div class="w-100" style="position: sticky; top: 20px; background-color: silver; padding: 20px 0; margin: 10px 0;">
+		<div id="visualization"></div>
+	
+</div>
+
+{% endif %}
+
+<ul id="tweets" class="tweets z-0">
+
+{% for tweet in tweets %}
+
+<li class="tweet d-flex flex-column mb-2 {% if tweet.is_marked %}marked{% endif %}">
+<script>
+	
+	
+	var feedItem = {{ tweet | tojson }};
+	var plotItems = [];
+	
+	var likesPoint = feed_item_to_likes(feedItem);
+	if (likesPoint) { plotItems.push(likesPoint); }
+	
+	var repliesPoint = feed_item_to_replies(feedItem);
+	if (repliesPoint) { plotItems.push(repliesPoint); }
+	
+	if (!plotItems.length) {
+		plotItems.push(feed_item_to_activity(feedItem))
+	}
+	
+	
+	
+	dataset.update(plotItems);
+
+</script>
+
+
+	{% if tweet.retweeted_by %}
+	<div class="d-flex flex-row">
+		<p class="text-end pe-3" style="width: 60px; max-width: 60px; min-width: 60px">
+			<i class="bi-repeat"></i>
+		</p>
+		<p class="flex-grow-1"><a class="moon-gray" href="{{ tweet.retweeted_by_url }}">{{ tweet.retweeted_by }} Retweeted</a></p>
+	</div>
+	{% endif %}
+	<div class="d-flex flex-row">
+
+		{% include "partial/timeline-tweet-bs.html" %}
+		
+		
+	</div>
+	<div class="d-flex flex-row">
+		<div class="tweet-actions-box d-flex flex-row justify-content-lg-between border-bottom g-2 p-2 flex-grow-1 flex-wrap" style="margin-left: 60px"">
+		
+		
+		{% if tweet.actions.view_replies %}
+		<a class="btn btn-sm btn-secondary m-1" href="{{ url_for(tweet.actions.view_replies.route, **tweet.actions.view_replies.route_params) }}">
+		
+		<i class="bi-chat"></i> 
+		replies
+		</a>
+
+		{% endif %}
+		
+
+		
+		
+		{% if show_thread_controls and tweet.conversation_id %}
+		{% with tweet=tweets[0] %}
+		{% if tweet.actions.view_thread %}
+		<a class="btn btn-sm btn-secondary m-1" href="{{ url_for(tweet.actions.view_thread.route, **tweet.actions.view_thread.route_params) }}">author thread</a>
+
+		{% endif %}
+		{% if tweet.actions.view_conversation %}
+		<a class="btn btn-sm btn-secondary m-1" href="{{ url_for(tweet.actions.view_conversation.route, **tweet.actions.view_conversation.route_params) }}">full convo</a>
+
+		{% endif %}
+		{% endwith %}
+		{% endif %}
+		
+		{% if tweet.actions.view_activity %}
+		<a class="btn btn-sm btn-secondary m-1" href="{{ url_for(tweet.actions.view_activity.route, **tweet.actions.view_activity.route_params) }}">
+		
+		<i class="bi-graph-up"></i>
+		activity
+		</a>
+
+		{% endif %}
+		
+		{% if tweet.actions.retweet %}
+		<a class="btn btn-sm btn-secondary m-1" hx-post="{{ url_for(tweet.actions.retweet.route, **tweet.actions.retweet.route_params) }}">
+		
+		<i class="bi-repeat"></i> 
+		retweet
+		
+		</a>
+
+		{% endif %}
+		
+		{% if tweet.actions.bookmark %}
+		<a class="btn btn-sm btn-secondary m-1" hx-post="{{ url_for(tweet.actions.bookmark.route, **tweet.actions.bookmark.route_params) }}">
+		
+		<i class="bi-bookmark"></i>
+		bookmark
+		
+		</a>
+		{% if tweet.actions.delete_bookmark %}
+		<a class="btn btn-sm btn-secondary m-1" hx-delete="{{ url_for(tweet.actions.delete_bookmark.route, **tweet.actions.delete_bookmark.route_params) }}">-</a>
+		{% endif %}
+
+		{% endif %}
+		
+		<a class="btn btn-sm btn-secondary m-1" class="tweet-action copy-formatted" href="javascript:copyTweetToClipboard('{{ tweet.id }}')">copy formatted</a>
+		{% if notes_app_url %}
+
+		<a class="btn btn-sm btn-secondary m-1" class="tweet-action swipe-to-note" href="javascript:swipeTweetToNotesApp('{{ tweet.id }}')">swipe to note</a>
+		{% endif %}
+
+		</div>
+	</div>
+	
+
+	</li>
+
+{% endfor %}
+
+
+
+{% if query.next_data_url %}
+
+	<li style="height: 50px; vertical-align: middle"
+		hx-get="{{ query.next_data_url }}"
+		hx-trigger="revealed"
+		hx-swap="outerHTML"
+		hx-select="ul#tweets > li"
+		>
+		<center style="height: 100%">
+
+		<span class="js-only">
+		Loading more tweets...
+		</span>
+
+		</center>
+	</li>
+	
+{% elif query.next_page_url %}
+	<li style="height: 50px; vertical-align: middle"
+		>
+		<center style="height: 100%">
+		<a href="{{ query.next_page_url }}">
+		Go to Next Page
+		</a>
+		
+		
+		</center>
+	
+	</li>
+	
+{% endif %}
+	<li style="display: none">
+		<script>
+			// https://stackoverflow.com/questions/22663353/algorithm-to-remove-extreme-outliers-in-array
+			// we should remove outliers on the X axis. That will mainly be RTs with old dates.
+			// we might also be able to get the date of RT as opposed to OG tweet date.
+			
+			// https://towardsdatascience.com/ways-to-detect-and-remove-the-outliers-404d16608dba
+			var profileDataEl = document.querySelector('#profile-data');
+			
+			if (window['dataset'] && profileDataEl) {
+				profileDataEl.innerHTML = dataset.get().filter(i => 'public_metrics' in i).map(i => i.public_metrics.like_count).join(', ');
+			}
+
+			{% if visjs_enabled %}
+			if (window.profileActivity) {
+				window.profileActivity.fit()
+			}
+			{% endif %}
+		</script>
+	</li>
+</ul>
+
+			{% if twitter_live_enabled and visjs_enabled and not skip_plot %}
+			
+	<script>
+
+
+
+function onClick (e) {
+  // we need to scan the dataset between min/max x/y
+  // 
+  // TODO we want to scale these based on the zoom level / pixel values
+  //
+  // FIXME sometimes we get several points:
+  // We could also go for the closest point within the bound.
+  // Perhaps cycle through upon multiple clicks.
+  // For now we can just zoom in closer.
+  //
+  // range: graph2d.components[3].options.dataAxis.left.range.max
+  // fixing this is lower priority since it is currently static.
+  graph2d = window.profileActivity;
+  
+  var timeWindow = graph2d.getWindow();
+  var windowInSeconds = (timeWindow.end - timeWindow.start) / 1000;
+  
+  var pixelWidth = graph2d.dom.centerContainer.offsetWidth;
+  
+  var secondsPerPixel = windowInSeconds / pixelWidth;
+  
+  console.log('secondsPerPixel = ' + secondsPerPixel);
+  
+  
+  //var MAX_TIME_DIFF  = 1000 * 60 * 60;
+  
+  var MAX_TIME_DIFF = 10 * secondsPerPixel * 1000;
+  var MAX_VALUE_DIFF = 10;
+  
+  console.log(`click. value=${e.value[0]}, time=${e.time}`);
+  console.log(e);
+  
+  var nearbyItems = dataset.get({filter: function (item) {
+	var timeDiff = new Date(item.x).getTime() - e.time.getTime();
+	var valueDiff = item.y - e.value[0];
+	
+	return Math.abs(timeDiff) < MAX_TIME_DIFF 
+		   && Math.abs(valueDiff) < MAX_VALUE_DIFF;
+	
+  }});
+  
+  //console.log([e.time, e.value[0]]);
+  console.log('nearby points:');
+  console.log(nearbyItems);
+}
+
+
+var container = document.getElementById('visualization');
+
+
+
+var options = {
+  sort: false,
+  sampling:false,
+  style:'points',
+  dataAxis: {
+		  width: '88px',
+
+	  //visible: false,
+	  left: {
+		  range: {
+			  min: 0, max: 10
+		  }
+	  }
+  },  
+  drawPoints: {
+	  enabled: true,
+	  size: 6,
+	  style: 'circle' // square, circle
+  },
+  defaultGroup: 'Scatterplot',
+  graphHeight: '50px',
+  width: '100%'
+};
+
+var groups = [{id: 'feed_item'}];
+
+window.profileActivity = new vis.Graph2d(container, window.dataset, groups, options);
+
+window.profileActivity.on('click', onClick);
+
+	</script>
+			
+			{% endif %}

+ 96 - 0
templates/partial/user-card-bs.html

@@ -0,0 +1,96 @@
+<div class="user w-100 p-1" style="border: 1px solid black;">
+	
+	<div style="float: right; margin-top: 0; text-align: right">
+		{% if user.profile_image_url %}
+		<img style="float: right; margin-left: 4px;" src="{{ user.profile_image_url }}">
+	{% endif %}
+
+	{% if user.username %}
+	<pre style="display: inline; font-weight: bold; ">@{{ user.username }}</pre>
+	<br>
+	{% endif %}
+	<span><pre style="display: inline;">{{ user.id }}</pre></span>
+	
+	</div>
+	
+	<p style="margin-top: 0">
+		
+		
+		{% if user.actions and user.actions.view_profile %}
+		{% with view_profile=user.actions.view_profile %}
+		<a href="{{ url_for( view_profile.route, **view_profile.route_params ) }}">
+		{% endwith %}
+		{% elif user.url %}
+		<a href="{{ user.url }}">
+		{% else %}
+		<a href="{{ url_for('twitter_v2_facade.get_profile_html', user_id=user.id) }}">
+		{% endif %}
+		<strong>{{ user.name }}</strong></a>
+		
+		{% if user.verified %}
+		<small>[verified]</small>
+		{% endif %}
+		
+		{% if user.protected %}
+		<small>[protected]</small>
+		{% endif %}
+	</p>
+	
+		
+	{% if user.description %}
+	<div>
+	
+		{{ user.description }}
+	</div>
+	{% endif %}
+	
+	
+	{% if user.pinned_tweet_id %}
+	<div>
+		<p>Pinned tweet:</p>
+		<div style="border: 1px solid black">{{ user.pinned_tweet_id }}</div>
+	</div>
+	{% endif %}
+	
+	<dl>
+		{% if user.created_at %}
+		<dt>Member since</dt>
+		<dd>{{ user.created_at }}</dd>
+		{% endif %}
+		
+		{% if user.website %}
+		<dt>Website</dt>
+		<dd><a href="{{ user.website }}">{{ user.website }}</a></dd>
+		{% endif %}
+		
+		{% if user.location %}
+		<dt>Location</dt>
+		<dd>{{ user.location }}</dd>
+		{% endif %}
+		
+		{% if user.public_metrics %}
+		
+		{% if user.public_metrics.tweet_count %}
+		<dt>Tweets</dt>
+		<dd>{{ user.public_metrics.tweet_count }}</dd>
+		{% endif %}
+		
+		{% if user.public_metrics.followers_count %}
+		<dt>Followers</dt>
+		<dd>{{ user.public_metrics.followers_count }}</dd>
+		{% endif %}
+		
+		{% if user.public_metrics.following_count %}
+		<dt>Following</dt>
+		<dd>{{ user.public_metrics.following_count }}</dd>
+		{% endif %}
+		
+		{% if user.public_metrics.listed_count %}
+		<dt>Listed</dt>
+		<dd>{{ user.public_metrics.listed_count }}</dd>
+		{% endif %}
+		
+		{% endif %}
+	</dl>
+	
+</div>

+ 15 - 0
templates/tweet-collection-bs.html

@@ -0,0 +1,15 @@
+{% extends "base-bs.html" %}
+
+{% block head %}
+	<title>Tweet Library: {{ user.id }}</title>
+{% endblock %}
+
+
+{% block content %}
+
+	{% include "partial/page-nav-bs.html" %}
+	
+	
+	{% include "partial/tweets-timeline-bs.html" %}
+	
+{% endblock %}

+ 59 - 0
templates/user-profile-bs.html

@@ -0,0 +1,59 @@
+{% extends "base-bs.html" %}
+
+{% block head %}
+	<title>Profile: {{ user.id }}</title>
+
+
+	
+{% endblock %}
+
+{% block content %}
+
+
+
+	<div class="w-100">
+		
+		{% include "partial/user-card-bs.html" %}
+		
+		{% if False %}
+		
+		{% if brand %}
+		{% include "partial/brand-info.html" %}
+		{% endif %}
+		{% endif %}
+		
+		<div class="w-100" style="height: 80px;">
+
+			{% include "partial/page-nav-bs.html" %}
+
+			{% if False %}
+			<div class="w-100">
+			<form action="{{ url_for('twitter_v2_facade.get_profile_html', user_id=user.id) }}" method="GET">
+			
+			<input type="hidden" name="me" value="{{ me }}">
+			<input type="hidden" name="user_id" value="{{ user.id }}">
+			
+			<input type="checkbox" name="exclude_replies" value="1" {% if request.args.exclude_replies %}checked{% endif %}> No replies 
+			|
+			<input type="checkbox" name="only_media" value="1" {%if request.args.only_media %}checked{% endif %}> Only media
+			<br>
+			<button type="submit">Filter</button>
+			</form>
+			</div>
+			{% endif %}
+		</div>
+		
+		
+
+
+
+	
+		{% with show_thread_controls=True %}
+		
+		{% include "partial/tweets-timeline-bs.html" %}
+		
+		{% endwith %}
+		
+
+	</div>
+{% endblock %}

+ 16 - 5
tweet_source.py

@@ -518,7 +518,8 @@ class ApiV2TweetSource:
             max_results=max_results, pagination_token=pagination_token, since_id=since_id, return_dataclass=return_dataclass)
             max_results=max_results, pagination_token=pagination_token, since_id=since_id, return_dataclass=return_dataclass)
     
     
     def get_user_timeline (self, user_id,
     def get_user_timeline (self, user_id,
-                          max_results = 10, pagination_token = None, since_id = None,
+                          max_results = 10, pagination_token = None,
+                          since_id = None,
                           non_public_metrics=False,
                           non_public_metrics=False,
                           exclude_replies=False,
                           exclude_replies=False,
                           exclude_retweets=False,
                           exclude_retweets=False,
@@ -802,7 +803,8 @@ class ApiV2TweetSource:
             max_results=max_results, pagination_token=pagination_token, since_id=since_id, return_dataclass=return_dataclass)
             max_results=max_results, pagination_token=pagination_token, since_id=since_id, return_dataclass=return_dataclass)
         
         
     def get_liking_users (self, tweet_id,
     def get_liking_users (self, tweet_id,
-            max_results = None, pagination_token = None):
+            max_results = None, pagination_token = None,
+            return_dataclass=False):
         #  GET /2/tweets/:id/liking_users
         #  GET /2/tweets/:id/liking_users
         # User rate limit (User context): 75 requests per 15-minute window per each authenticated user
         # User rate limit (User context): 75 requests per 15-minute window per each authenticated user
 
 
@@ -810,7 +812,7 @@ class ApiV2TweetSource:
         
         
         user_fields = ["id", "created_at", "name", "username", "location", "profile_image_url", "verified", "description", "public_metrics", "protected", "pinned_tweet_id", "url"]
         user_fields = ["id", "created_at", "name", "username", "location", "profile_image_url", "verified", "description", "public_metrics", "protected", "pinned_tweet_id", "url"]
         
         
-        expansions = ["author_id"]
+        expansions = []
         
         
         params = cleandict({
         params = cleandict({
             "user.fields": ','.join(user_fields),
             "user.fields": ','.join(user_fields),
@@ -825,9 +827,18 @@ class ApiV2TweetSource:
         
         
         resp = requests.get(url, headers=headers, params=params)
         resp = requests.get(url, headers=headers, params=params)
         
         
-        response_json = json.loads(resp.text)
+        result = json.loads(resp.text)
+
+        typed_result = from_dict(data_class=UserSearchResponse, data=result)
+        
+        #print(typed_result)
         
         
-        return response_json
+        if return_dataclass:
+            return typed_result
+        
+        result = cleandict(asdict(typed_result))
+        
+        return result
         
         
     def like_tweet (self, tweet_id):
     def like_tweet (self, tweet_id):
         #  POST /2/users/:user_id/likes
         #  POST /2/users/:user_id/likes

+ 32 - 15
twitter_v2_facade.py

@@ -262,7 +262,6 @@ def delete_tweet_bookmark (tweet_id):
     return jsonify(response_body)
     return jsonify(response_body)
 
 
 
 
-
 @twitter_app.route('/tweet/<tweet_id>.html', methods=['GET'])
 @twitter_app.route('/tweet/<tweet_id>.html', methods=['GET'])
 def get_tweet_html (tweet_id):
 def get_tweet_html (tweet_id):
     
     
@@ -321,7 +320,7 @@ def get_tweet_html (tweet_id):
     if replies_response and replies_response.meta and replies_response.meta.result_count:
     if replies_response and replies_response.meta and replies_response.meta.result_count:
     
     
         includes = replies_response.includes
         includes = replies_response.includes
-        tweets = list(map(lambda t: tweet_model_dc_vm(includes, t, g.me), replies_response.data)) + tweets
+        tweets = list(map(lambda t: tweet_model_dc_vm(includes, t, g.me, expand_path=request.args.get('expand'), reply_depth=1), replies_response.data)) + tweets
         
         
         next_token = replies_response.meta.next_token
         next_token = replies_response.meta.next_token
     
     
@@ -429,7 +428,7 @@ def get_tweet_html (tweet_id):
                     if expanded_replies_response.data:
                     if expanded_replies_response.data:
                         print('we got expanded responses data')
                         print('we got expanded responses data')
                         
                         
-                        children =  list(map(lambda t: tweet_model_dc_vm(expanded_replies_response.includes, t, g.me), expanded_replies_response.data))
+                        children =  list(map(lambda t: tweet_model_dc_vm(expanded_replies_response.includes, t, g.me, expand_path=request.args.get('expand'), reply_depth=1), expanded_replies_response.data))
                         children = list(map(reply_to_thread_item, children))
                         children = list(map(reply_to_thread_item, children))
                 
                 
                 
                 
@@ -443,7 +442,7 @@ def get_tweet_html (tweet_id):
             )
             )
             return render_template('tweet-thread.html', user = user, root = root, query = query, page_nav=page_nav, skip_embed_replies=skip_embed_replies, opengraph_info=opengraph_info)
             return render_template('tweet-thread.html', user = user, root = root, query = query, page_nav=page_nav, skip_embed_replies=skip_embed_replies, opengraph_info=opengraph_info)
         else:
         else:
-            return render_template('tweet-collection.html', user = user, tweets = tweets, query = query, page_nav=page_nav, skip_embed_replies=skip_embed_replies, opengraph_info=opengraph_info)
+            return render_template('tweet-collection-bs.html', user = user, tweets = tweets, query = query, page_nav=page_nav, skip_embed_replies=skip_embed_replies, opengraph_info=opengraph_info)
 
 
 
 
 
 
@@ -464,7 +463,7 @@ def get_followers_html (user_id):
         with open(f'.data/cache/followers_{user_id}_{use_cache}.json', 'rt') as f:
         with open(f'.data/cache/followers_{user_id}_{use_cache}.json', 'rt') as f:
             response_json = json.load(f)
             response_json = json.load(f)
     else:
     else:
-        response_json = social_source.get_followers(user_id, return_dataclass=True)
+        response_json = social_source.get_followers(user_id, max_results=1000, return_dataclass=True)
     
     
     if not use_cache:
     if not use_cache:
         ts = int(time.time() * 1000)
         ts = int(time.time() * 1000)
@@ -495,7 +494,7 @@ def get_following_html (user_id):
     
     
     social_source = TwitterApiV2SocialGraph(token)
     social_source = TwitterApiV2SocialGraph(token)
     
     
-    response_json = social_source.get_following(user_id, return_dataclass=True)
+    response_json = social_source.get_following(user_id, max_results=1000, return_dataclass=True)
     
     
     ts = int(time.time() * 1000)
     ts = int(time.time() * 1000)
     with open(f'{DATA_DIR}/cache/following_{user_id}_{ts}.json', 'wt') as f:
     with open(f'{DATA_DIR}/cache/following_{user_id}_{ts}.json', 'wt') as f:
@@ -552,7 +551,7 @@ def user_model_dc (user):
     
     
     return fsu
     return fsu
 
 
-def tweet_model_dc_vm (includes: TweetExpansions, tweet: Tweet, me, my_url_for=url_for, reply_depth=0) -> FeedItem:
+def tweet_model_dc_vm (includes: TweetExpansions, tweet: Tweet, me, my_url_for=url_for, reply_depth=0, expand_path=None) -> FeedItem:
     
     
     # retweeted_by, avi_icon_url, display_name, handle, created_at, text
     # retweeted_by, avi_icon_url, display_name, handle, created_at, text
     
     
@@ -574,13 +573,23 @@ def tweet_model_dc_vm (includes: TweetExpansions, tweet: Tweet, me, my_url_for=u
         quoted = list(filter(lambda r: r.type == 'quoted', tweet.referenced_tweets))
         quoted = list(filter(lambda r: r.type == 'quoted', tweet.referenced_tweets))
         replied_to = list(filter(lambda r: r.type == 'replied_to', tweet.referenced_tweets))
         replied_to = list(filter(lambda r: r.type == 'replied_to', tweet.referenced_tweets))
     
     
+    if reply_depth:
+        if expand_path:
+            expand_path += f',{tweet.id}'
+        else:
+            expand_path = tweet.id
+    
     actions = {
     actions = {
-        'view_replies': FeedItemAction('twitter_v2_facade.get_tweet_html', {'tweet_id': tweet.id, 'view': 'replies'}),
+        'view_replies': FeedItemAction('twitter_v2_facade.get_tweet_html', {'tweet_id': tweet.conversation_id, 'view': 'replies', 'expand': expand_path}),
         
         
         'view_thread': FeedItemAction('twitter_v2_facade.get_tweet_html', {'tweet_id': tweet.conversation_id, 'view': 'thread'}),
         'view_thread': FeedItemAction('twitter_v2_facade.get_tweet_html', {'tweet_id': tweet.conversation_id, 'view': 'thread'}),
         'view_conversation': FeedItemAction('twitter_v2_facade.get_tweet_html', {'tweet_id': tweet.conversation_id, 'view': 'conversation'}),
         'view_conversation': FeedItemAction('twitter_v2_facade.get_tweet_html', {'tweet_id': tweet.conversation_id, 'view': 'conversation'}),
     }
     }
     
     
+    if reply_depth:
+        vr = actions['view_replies']
+        url = url_for(vr.route, **vr.route_params)
+    
     if g.get('twitter_user'):
     if g.get('twitter_user'):
         actions.update(
         actions.update(
             bookmark = FeedItemAction('twitter_v2_facade.post_tweet_bookmark', {'tweet_id': tweet.id}),
             bookmark = FeedItemAction('twitter_v2_facade.post_tweet_bookmark', {'tweet_id': tweet.id}),
@@ -929,10 +938,13 @@ def get_bookmarks_html ():
     token = g.twitter_user['access_token']
     token = g.twitter_user['access_token']
     
     
     pagination_token = request.args.get('pagination_token')
     pagination_token = request.args.get('pagination_token')
+    max_results = int(request.args.get('limit', 10))
     
     
     tweet_source = ApiV2TweetSource(token)
     tweet_source = ApiV2TweetSource(token)
     response_json = tweet_source.get_bookmarks(user_id,
     response_json = tweet_source.get_bookmarks(user_id,
-                                                    pagination_token = pagination_token, return_dataclass=True)
+                                                    pagination_token = pagination_token, return_dataclass=True,
+                                                    max_results=max_results
+                                                    )
     
     
     #print(response_json)
     #print(response_json)
     
     
@@ -946,18 +958,23 @@ def get_bookmarks_html ():
         query = {
         query = {
             **query,
             **query,
             
             
-            'next_data_url': url_for('.get_bookmarks_html', user_id=user_id, pagination_token=next_token),
-            'next_page_url': url_for('.get_bookmarks_html', user_id=user_id, pagination_token=next_token)
+            'next_data_url': url_for('.get_bookmarks_html', user_id=user_id, pagination_token=next_token, limit=max_results),
+            'next_page_url': url_for('.get_bookmarks_html', user_id=user_id, pagination_token=next_token, limit=max_results)
         }
         }
     
     
     user = {
     user = {
             'id': user_id
             'id': user_id
         }
         }
     
     
+    ts = int(time.time() * 1000)
+    with open(f'{DATA_DIR}/cache/bookmarks_{user_id}_{ts}_{pagination_token}.json', 'wt') as f:
+        f.write(json.dumps(response_json))
+    
+    
     if 'HX-Request' in request.headers:
     if 'HX-Request' in request.headers:
-        return render_template('partial/tweets-timeline.html', user = user, tweets = tweets, query = query)
+        return render_template('partial/tweets-timeline-bs.html', user = user, tweets = tweets, query = query)
     else:
     else:
-        return render_template('tweet-collection.html', user = user, tweets = tweets, query = query)
+        return render_template('tweet-collection-bs.html', user = user, tweets = tweets, query = query)
 
 
 
 
 
 
@@ -1043,7 +1060,7 @@ def get_profile_html (user_id):
         profile_user = {
         profile_user = {
             'id': user_id
             'id': user_id
         }
         }
-        return render_template('partial/tweets-timeline.html', user = profile_user, tweets = tweets, query = query)
+        return render_template('partial/tweets-timeline-bs.html', user = profile_user, tweets = tweets, query = query)
     else:
     else:
         # FIXME the user is probably present in the tweet expansions info.
         # FIXME the user is probably present in the tweet expansions info.
         
         
@@ -1133,7 +1150,7 @@ def get_profile_html (user_id):
                 brand_info = fetch_brand_info(brand)
                 brand_info = fetch_brand_info(brand)
                 brand_info.update({'brand': brand, 'twitter': None})
                 brand_info.update({'brand': brand, 'twitter': None})
         
         
-        return render_template('user-profile.html', user = user, tweets = tweets, query = query, opengraph_info=opengraph_info, page_nav = page_nav, top8=top8, **brand_info)
+        return render_template('user-profile-bs.html', user = user, tweets = tweets, query = query, opengraph_info=opengraph_info, page_nav = page_nav, top8=top8, **brand_info)
 
 
 
 
 
 

+ 3 - 0
view_model.py

@@ -56,6 +56,9 @@ class FeedItemAction:
     route: str
     route: str
     route_params: Dict
     route_params: Dict
 
 
+# intended as external resources,
+# not first class display.
+# PDF, chat, embedded image (iframe)
 @dataclass
 @dataclass
 class FeedItemAttachment:
 class FeedItemAttachment:
     name: str
     name: str

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff