소스 검색

giao diện và sql kiểm tra

student 3 주 전
부모
커밋
e85e572a2b

+ 2 - 0
Kitty_Fall/Kitty_Fall/Common/Http/Apis/Request/UserGameActivityHistory.cs

@@ -50,6 +50,8 @@ namespace Common.Http.Apis.Request
         public string picture { get; set; } = "";
         public long totalTurn { get; set; }
         public long totalPoint { get; set; }
+        public decimal totalDataMb { get; set; }
+        public decimal totalLoyaltyPoints { get; set; }
     }
 
     public class UserGameActivityHistoryData

+ 27 - 1
Kitty_Fall/Kitty_Fall/Kitty_Fall.Apis/Business/User/UserBusinessImpl.cs

@@ -354,6 +354,30 @@ namespace Kitty_Fall.Apis.Business.User
                         && x.StartTime < startOfNextMonth)
                     .SumAsync(x => (long?)x.TotalScore) ?? 0;
 
+                // DATA va LOYALTY la hai loai thuong ngoai SCORE game. Tong thang
+                // lay tu attempt da trung de menu account hien thi dung ket qua choi,
+                // khong phu thuoc trang thai worker da payout hay chua.
+                var monthlyExternalRewards = await dbContext.GameLevelAttempts.AsNoTracking()
+                    .Where(x => x.Msisdn == msisdn
+                        && x.Status == 2
+                        && x.IsValid == true
+                        && x.SubmitTime >= startOfMonth
+                        && x.SubmitTime < startOfNextMonth
+                        && (x.RewardType == "DATA" || x.RewardType == "LOYALTY"))
+                    .GroupBy(x => x.RewardType)
+                    .Select(x => new
+                    {
+                        RewardType = x.Key,
+                        TotalValue = x.Sum(y => y.RewardValue ?? 0)
+                    })
+                    .ToListAsync();
+                var totalDataMb = monthlyExternalRewards
+                    .Where(x => x.RewardType == "DATA")
+                    .Sum(x => x.TotalValue);
+                var totalLoyaltyPoints = monthlyExternalRewards
+                    .Where(x => x.RewardType == "LOYALTY")
+                    .Sum(x => x.TotalValue);
+
                 return CommonLogic.BuildResponse(
                     url,
                     json,
@@ -366,7 +390,9 @@ namespace Kitty_Fall.Apis.Business.User
                         msisdn = userAccounts.Msisdn,
                         totalTurn,
                         totalPoint,
-                        totalpoin = totalPoint
+                        totalpoin = totalPoint,
+                        totalDataMb,
+                        totalLoyaltyPoints
                     }
                   );
             }

+ 10 - 0
Kitty_Fall/Kitty_Fall/Kitty_Fall.Website/Controllers/HomeController.cs

@@ -293,12 +293,16 @@ namespace Kitty_Fall.Website.Controllers
                     ?? data?["Totalpoin"]
                     ?? data?["totalPointsThisMonth"]
                     ?? data?["TotalPointsThisMonth"]);
+                var loadedDataMb = ReadDecimal(data?["totalDataMb"] ?? data?["TotalDataMb"]);
+                var loadedLoyaltyPoints = ReadDecimal(data?["totalLoyaltyPoints"] ?? data?["TotalLoyaltyPoints"]);
                 var regInfos = data?["regInfos"] ?? data?["RegInfos"];
 
                 HttpContext.Session.SetComplexData("msisdn", loadedMsisdn);
                 HttpContext.Session.SetComplexData("profilePicture", loadedPicture);
                 HttpContext.Session.SetComplexData("totalTurn", loadedTurn);
                 HttpContext.Session.SetComplexData("totalPoint", loadedPoint);
+                HttpContext.Session.SetComplexData("totalDataMb", loadedDataMb);
+                HttpContext.Session.SetComplexData("totalLoyaltyPoints", loadedLoyaltyPoints);
                 if (regInfos != null)
                 {
                     HttpContext.Session.SetComplexData("regInfos", regInfos);
@@ -392,6 +396,12 @@ namespace Kitty_Fall.Website.Controllers
             return long.TryParse(token.ToString(), out var value) ? value : 0;
         }
 
+        private static decimal ReadDecimal(JToken? token)
+        {
+            if (token == null) return 0;
+            return decimal.TryParse(token.ToString(), out var value) ? value : 0;
+        }
+
         private static string ReadPackageString(Dictionary<string, object?> package, string key)
         {
             return package.TryGetValue(key, out var value) ? value?.ToString() ?? "" : "";

+ 18 - 1
Kitty_Fall/Kitty_Fall/Kitty_Fall.Website/Language/Lang.Designer.cs

@@ -1277,5 +1277,22 @@ namespace Kitty_Fall.Website.Language {
             get {
                 return ResourceManager.GetString("GuideMonthlyPrizeLimit", resourceCulture);
             }
-        }    }
+        }
+        /// <summary>
+        ///   Looks up a localized string for AccountLoyaltyPoints.
+        /// </summary>
+        public static string AccountLoyaltyPoints {
+            get {
+                return ResourceManager.GetString("AccountLoyaltyPoints", resourceCulture);
+            }
+        }
+        /// <summary>
+        ///   Looks up a localized string for AccountTotalLoyaltyPoints.
+        /// </summary>
+        public static string AccountTotalLoyaltyPoints {
+            get {
+                return ResourceManager.GetString("AccountTotalLoyaltyPoints", resourceCulture);
+            }
+        }
+    }
 }

+ 6 - 0
Kitty_Fall/Kitty_Fall/Kitty_Fall.Website/Language/Lang.resx

@@ -543,4 +543,10 @@
   <data name="GuideMonthlyPrizeLimit" xml:space="preserve">
     <value>Each subscriber is eligible to receive a monthly prize only once within the most recent 3-month period.</value>
   </data>
+  <data name="AccountLoyaltyPoints" xml:space="preserve">
+    <value>LOYALTY POINTS</value>
+  </data>
+  <data name="AccountTotalLoyaltyPoints" xml:space="preserve">
+    <value>Total loyalty points</value>
+  </data>
 </root>

+ 6 - 0
Kitty_Fall/Kitty_Fall/Kitty_Fall.Website/Language/Lang.vi.resx

@@ -543,4 +543,10 @@
   <data name="GuideMonthlyPrizeLimit" xml:space="preserve">
     <value>Each subscriber is eligible to receive a monthly prize only once within the most recent 3-month period.</value>
   </data>
+  <data name="AccountLoyaltyPoints" xml:space="preserve">
+    <value>LOYALTY POINTS</value>
+  </data>
+  <data name="AccountTotalLoyaltyPoints" xml:space="preserve">
+    <value>Total loyalty points</value>
+  </data>
 </root>

+ 109 - 30
Kitty_Fall/Kitty_Fall/Kitty_Fall.Website/Views/Shared/_Layout.cshtml

@@ -1,4 +1,7 @@
 @using Kitty_Fall.Website.Language
+@using Common.Extension
+@using Newtonsoft.Json.Linq
+@inject Microsoft.Extensions.Configuration.IConfiguration Configuration
 <!DOCTYPE html>
 <html lang="en">
 <head>
@@ -23,25 +26,99 @@
         ? "bottom-nav-shell home-screen scale-floor"
         : "bottom-nav-shell scale-floor";
     var currentCulture = System.Globalization.CultureInfo.CurrentUICulture.Name.ToLower().StartsWith("vi") ? "vi" : "en";
+    var accountMsisdn = Context.Session.GetComplexData<string>("msisdn") ?? "";
+    var accountAvatar = Url.Content("~/kitty/assets/kitty-fall/account-cat-avatar.png");
+    var accountScore = Context.Session.GetComplexData<long?>("totalPoint") ?? 0;
+    var accountDataMb = Context.Session.GetComplexData<decimal?>("totalDataMb") ?? 0;
+    var accountLoyalty = Context.Session.GetComplexData<decimal?>("totalLoyaltyPoints") ?? 0;
+    var regInfos = Context.Session.GetComplexData<JArray>("regInfos");
+    var activeReg = regInfos?.FirstOrDefault(x =>
+        bool.TryParse((x["status"] ?? x["Status"])?.ToString(), out var active) && active);
+    var packageNameRaw = (activeReg?["productName"] ?? activeReg?["ProductName"])?.ToString() ?? "";
+    var packageName = string.IsNullOrWhiteSpace(packageNameRaw)
+        ? "No active package"
+        : packageNameRaw.Contains("DAYLY", StringComparison.OrdinalIgnoreCase)
+            || packageNameRaw.Contains("DAILY", StringComparison.OrdinalIgnoreCase)
+                ? "Daily Package"
+                : packageNameRaw.Replace("_", " ");
+    var packageTurns = (activeReg?["numberSpin"] ?? activeReg?["NumberSpin"])?.ToString() ?? "0";
+    var cancelKeyword = Configuration["AccountMenu:CancelKeyword"] ?? "OFF";
+    var cancelShortCode = Configuration["AccountMenu:CancelShortCode"] ?? "562";
+    var monthStart = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1);
+    var monthEnd = monthStart.AddMonths(1).AddDays(-1);
 }
     @RenderBody()
 
-    <div class="language-menu-modal" data-language-menu aria-hidden="true">
-        <button class="language-menu-backdrop" type="button" data-language-close aria-label="Close language menu"></button>
-        <section class="language-menu-card" role="dialog" aria-modal="true" aria-label="Language">
-            <button class="language-menu-close" type="button" data-language-close aria-label="Close language menu">x</button>
-            <div class="language-menu-title">Language</div>
-            <div class="language-menu-options">
-                <button class="language-menu-option @(currentCulture == "en" ? "active" : null)" type="button" onclick="changeLang('en')">
-                    <img src="https://flagcdn.com/w40/gb.png" alt="English">
-                    <span>English</span>
-                    <strong>EN</strong>
-                </button>
-                <button class="language-menu-option @(currentCulture == "vi" ? "active" : null)" type="button" onclick="changeLang('vi')">
-                    <img src="https://flagcdn.com/w40/mm.png" alt="Myanmar">
-                    <span>Myanmar</span>
-                    <strong>MM</strong>
-                </button>
+    <div class="account-menu-modal" data-account-menu aria-hidden="true">
+        <button class="account-menu-backdrop" type="button" data-account-close aria-label="Close account menu"></button>
+        <section class="account-menu-card" role="dialog" aria-modal="true" aria-label="Account">
+            <div class="account-menu-ribbon"><span>ACCOUNT</span></div>
+            <button class="account-menu-close" type="button" data-account-close aria-label="Close account menu">×</button>
+
+            <div class="account-menu-content">
+                <section class="account-profile-card">
+                    <img class="account-profile-avatar" src="@accountAvatar" alt="Account avatar">
+                    <div>
+                        <small>ACCOUNT (PHONE NUMBER)</small>
+                        <strong>@accountMsisdn</strong>
+                        <p>This is your default account.</p>
+                    </div>
+                </section>
+
+                <section class="account-summary-card">
+                    <h2><span>▣</span> TOTAL THIS MONTH</h2>
+                    <div class="account-summary-grid">
+                        <div class="account-summary-item is-score">
+                            <img src="~/kitty/assets/kitty-fall/coin.png" alt="">
+                            <span><small>POINTS</small><strong>@accountScore.ToString("N0")</strong><em>Total points</em></span>
+                        </div>
+                        <div class="account-summary-item is-data">
+                            <i class="account-metric-icon is-data">MB</i>
+                            <span><small>DATA (MB)</small><strong>@accountDataMb.ToString("N0")</strong><em>Total data won</em></span>
+                        </div>
+                        <div class="account-summary-item is-loyalty">
+                            <i class="account-metric-icon is-loyalty">🐾</i>
+                            <span><small>@Lang.AccountLoyaltyPoints</small><strong>@accountLoyalty.ToString("N0")</strong><em>@Lang.AccountTotalLoyaltyPoints</em></span>
+                        </div>
+                    </div>
+                    <p class="account-summary-note">
+                        <span>i</span> Points, data and rewards are accumulated from
+                        @monthStart.ToString("dd/MM") - @monthEnd.ToString("dd/MM") and reset monthly.
+                    </p>
+                </section>
+
+                <section class="account-package-card">
+                    <span class="account-section-icon">★</span>
+                    <div><small>CURRENT PACKAGE</small><strong>@packageName</strong><p>@packageTurns plays per day</p></div>
+                </section>
+
+                <section class="account-cancel-card">
+                    <span class="account-section-icon">☏</span>
+                    <div>
+                        <small>HOW TO CANCEL</small>
+                        <p>If you want to cancel the service, please send</p>
+                        <strong>@cancelKeyword to @cancelShortCode</strong>
+                        <p>and follow the instructions.</p>
+                    </div>
+                    <img src="~/kitty/assets/kitty-fall/account-cat-cancel.png" alt="Kitty Fall cat">
+                </section>
+
+                <section class="account-language-card">
+                    <span class="account-section-icon">◎</span>
+                    <div class="account-language-main">
+                        <small>LANGUAGE</small>
+                        <div class="account-language-options">
+                            <button class="account-language-option @(currentCulture == "en" ? "active" : null)" type="button" onclick="changeLang('en')">
+                                <img src="~/kitty/assets/kitty-fall/flag-gb.svg" alt=""><strong>ENGLISH</strong><b>✓</b>
+                            </button>
+                            <button class="account-language-option @(currentCulture == "vi" ? "active" : null)" type="button" onclick="changeLang('vi')">
+                                <img src="~/kitty/assets/kitty-fall/flag-mm.svg" alt=""><strong>MYANMAR</strong><b>✓</b>
+                            </button>
+                        </div>
+                    </div>
+                </section>
+
+                <p class="account-security-note"><span>×</span>Your game experience and rewards are saved to this account securely.</p>
             </div>
         </section>
     </div>
@@ -86,37 +163,39 @@
         }
 
         (function () {
-            const languageMenu = document.querySelector("[data-language-menu]");
-            if (!languageMenu) return;
+            const accountMenu = document.querySelector("[data-account-menu]");
+            if (!accountMenu) return;
 
-            const openLanguageMenu = function (event) {
+            const openAccountMenu = function (event) {
                 event.preventDefault();
                 event.stopPropagation();
-                languageMenu.classList.add("is-open");
-                languageMenu.setAttribute("aria-hidden", "false");
+                accountMenu.classList.add("is-open");
+                accountMenu.setAttribute("aria-hidden", "false");
+                document.body.classList.add("has-account-menu");
                 requestAnimationFrame(function () {
-                    languageMenu.querySelector(".language-menu-option.active")?.focus({ preventScroll: true });
+                    accountMenu.querySelector(".account-menu-close")?.focus({ preventScroll: true });
                 });
             };
 
-            const closeLanguageMenu = function (event) {
+            const closeAccountMenu = function (event) {
                 event?.preventDefault();
                 event?.stopPropagation();
-                languageMenu.classList.remove("is-open");
-                languageMenu.setAttribute("aria-hidden", "true");
+                accountMenu.classList.remove("is-open");
+                accountMenu.setAttribute("aria-hidden", "true");
+                document.body.classList.remove("has-account-menu");
             };
 
             document.querySelectorAll(".menu-button").forEach(function (button) {
-                button.addEventListener("click", openLanguageMenu);
+                button.addEventListener("click", openAccountMenu);
             });
 
-            languageMenu.querySelectorAll("[data-language-close]").forEach(function (button) {
-                button.addEventListener("click", closeLanguageMenu);
+            accountMenu.querySelectorAll("[data-account-close]").forEach(function (button) {
+                button.addEventListener("click", closeAccountMenu);
             });
 
             document.addEventListener("keydown", function (event) {
-                if (event.key === "Escape" && languageMenu.classList.contains("is-open")) {
-                    closeLanguageMenu(event);
+                if (event.key === "Escape" && accountMenu.classList.contains("is-open")) {
+                    closeAccountMenu(event);
                 }
             });
         })();

+ 4 - 0
Kitty_Fall/Kitty_Fall/Kitty_Fall.Website/appsettings.json

@@ -2,6 +2,10 @@
   "url": "http://127.0.0.1:8309",
   "CountryCode": "95",
   "productName": "Kitty_Fall_DAYLY",
+  "AccountMenu": {
+    "CancelKeyword": "OFF",
+    "CancelShortCode": "562"
+  },
   "Logging": {
     "LogLevel": {
       "Default": "Information",

BIN
Kitty_Fall/Kitty_Fall/Kitty_Fall.Website/wwwroot/kitty/assets/kitty-fall/account-cat-avatar.png


BIN
Kitty_Fall/Kitty_Fall/Kitty_Fall.Website/wwwroot/kitty/assets/kitty-fall/account-cat-cancel.png


+ 10 - 0
Kitty_Fall/Kitty_Fall/Kitty_Fall.Website/wwwroot/kitty/assets/kitty-fall/flag-gb.svg

@@ -0,0 +1,10 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 36" role="img" aria-label="United Kingdom flag">
+  <clipPath id="a"><rect width="60" height="36" rx="3"/></clipPath>
+  <g clip-path="url(#a)">
+    <path fill="#012169" d="M0 0h60v36H0z"/>
+    <path stroke="#fff" stroke-width="8" d="m0 0 60 36M60 0 0 36"/>
+    <path stroke="#c8102e" stroke-width="4" d="m0 0 60 36M60 0 0 36"/>
+    <path stroke="#fff" stroke-width="12" d="M30 0v36M0 18h60"/>
+    <path stroke="#c8102e" stroke-width="7" d="M30 0v36M0 18h60"/>
+  </g>
+</svg>

+ 9 - 0
Kitty_Fall/Kitty_Fall/Kitty_Fall.Website/wwwroot/kitty/assets/kitty-fall/flag-mm.svg

@@ -0,0 +1,9 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 36" role="img" aria-label="Myanmar flag">
+  <clipPath id="a"><rect width="60" height="36" rx="3"/></clipPath>
+  <g clip-path="url(#a)">
+    <path fill="#fecb00" d="M0 0h60v12H0z"/>
+    <path fill="#34b233" d="M0 12h60v12H0z"/>
+    <path fill="#ea2839" d="M0 24h60v12H0z"/>
+    <path fill="#fff" d="m30 6.2 3.2 8.2 8.8.5-6.8 5.6 2.3 8.5-7.5-4.7-7.5 4.7 2.3-8.5-6.8-5.6 8.8-.5z"/>
+  </g>
+</svg>

+ 726 - 0
Kitty_Fall/Kitty_Fall/Kitty_Fall.Website/wwwroot/kitty/css/all.min.css

@@ -3926,3 +3926,729 @@ button {
         height: 46px;
     }
 }
+
+
+/* Account panel opened from the hamburger menu. */
+body.has-account-menu {
+    overflow: hidden;
+}
+
+.account-menu-modal {
+    position: fixed;
+    z-index: 10000;
+    inset: 0;
+    display: none;
+    place-items: center;
+    padding: 12px;
+}
+
+.account-menu-modal.is-open {
+    display: grid;
+}
+
+.account-menu-backdrop {
+    position: absolute;
+    inset: 0;
+    border: 0;
+    background: rgba(8, 39, 99, 0.52);
+    backdrop-filter: blur(2px);
+}
+
+.account-menu-card {
+    position: relative;
+    width: min(430px, calc(100vw - 24px));
+    max-height: calc(100dvh - 24px);
+    border: 3px solid #9bcdf5;
+    border-radius: 24px;
+    padding: 46px 12px 12px;
+    background:
+        linear-gradient(180deg, rgba(255,255,255,.94), rgba(239,249,255,.97)),
+        url("../assets/kitty-fall/bg.png") center/cover;
+    box-shadow:
+        inset 0 0 0 3px #fff,
+        0 12px 35px rgba(0, 54, 125, .38);
+    overflow-y: auto;
+    overscroll-behavior: contain;
+    color: #24375c;
+}
+
+.account-menu-card::-webkit-scrollbar {
+    width: 0;
+}
+
+.account-menu-ribbon {
+    position: absolute;
+    z-index: 2;
+    top: 15px;
+    left: 50%;
+    width: min(270px, 68%);
+    height: 54px;
+    transform: translateX(-50%);
+    display: grid;
+    place-items: center;
+    border: 2px solid #075bc7;
+    border-radius: 9px 9px 16px 16px;
+    background: linear-gradient(180deg, #29a0ff 0%, #0874e8 54%, #0053c5 100%);
+    color: #fff;
+    font-size: 25px;
+    line-height: 1;
+    font-weight: 1000;
+    font-style: italic;
+    text-shadow: 0 2px 0 #0759af;
+    box-shadow: 0 5px 0 rgba(0, 66, 167, .25);
+}
+
+.account-menu-ribbon::before,
+.account-menu-ribbon::after {
+    content: "🐾";
+    position: absolute;
+    top: 13px;
+    font-size: 20px;
+    filter: saturate(.8);
+}
+
+.account-menu-ribbon::before { left: 14px; }
+.account-menu-ribbon::after { right: 14px; }
+
+.account-menu-close {
+    position: absolute;
+    z-index: 4;
+    top: 12px;
+    right: 9px;
+    width: 42px;
+    height: 42px;
+    border: 2px solid #075bc7;
+    border-radius: 50%;
+    display: grid;
+    place-items: center;
+    padding: 0 0 3px;
+    background: linear-gradient(#42b1ff, #0876e6);
+    color: #fff;
+    font-size: 34px;
+    line-height: 1;
+    font-weight: 900;
+    box-shadow: 0 3px 0 rgba(0, 58, 154, .25);
+}
+
+.account-menu-content {
+    display: grid;
+    gap: 8px;
+    padding: 8px 2px 0;
+}
+
+.account-profile-card,
+.account-summary-card,
+.account-package-card,
+.account-cancel-card,
+.account-language-card,
+.account-security-note {
+    border: 1px solid #cfe3f8;
+    border-radius: 13px;
+    background: rgba(238, 248, 255, .92);
+}
+
+.account-profile-card {
+    min-height: 104px;
+    padding: 13px 14px;
+    display: grid;
+    grid-template-columns: 82px 1fr;
+    align-items: center;
+    gap: 12px;
+}
+
+.account-profile-avatar {
+    width: 76px;
+    height: 76px;
+    border: 2px solid #4ca7ee;
+    border-radius: 50%;
+    object-fit: cover;
+    background: #fff;
+    box-shadow: 0 3px 8px rgba(32, 104, 172, .2);
+}
+
+.account-profile-card small,
+.account-package-card small,
+.account-cancel-card small,
+.account-language-card small {
+    display: block;
+    color: #49617d;
+    font-size: 10px;
+    line-height: 1.2;
+    font-weight: 900;
+}
+
+.account-profile-card strong {
+    display: block;
+    margin-top: 5px;
+    color: #142c60;
+    font-size: 20px;
+    line-height: 1;
+    font-weight: 1000;
+}
+
+.account-profile-card p,
+.account-package-card p,
+.account-cancel-card p {
+    margin: 7px 0 0;
+    color: #52647c;
+    font-size: 10px;
+    line-height: 1.25;
+}
+
+.account-summary-card {
+    padding: 10px 12px 9px;
+    border-color: #f2ddae;
+    background: rgba(255, 251, 231, .94);
+}
+
+.account-summary-card h2 {
+    margin: 0 0 8px;
+    padding-bottom: 7px;
+    border-bottom: 1px solid #f1e2be;
+    color: #934f24;
+    font-size: 12px;
+    line-height: 1;
+    font-weight: 1000;
+}
+
+.account-summary-card h2 span {
+    color: #ffb21a;
+    margin-right: 5px;
+}
+
+.account-summary-grid {
+    display: grid;
+    grid-template-columns: repeat(3, minmax(0, 1fr));
+}
+
+.account-summary-item {
+    min-width: 0;
+    display: grid;
+    grid-template-columns: 36px minmax(0, 1fr);
+    align-items: center;
+    gap: 4px;
+    padding: 2px 7px;
+    border-right: 1px solid #eadfca;
+}
+
+.account-summary-item:first-child { padding-left: 0; }
+.account-summary-item:last-child {
+    padding-right: 0;
+    border-right: 0;
+}
+
+.account-summary-item > img {
+    width: 34px;
+    height: 34px;
+    object-fit: contain;
+}
+
+.account-summary-item span {
+    min-width: 0;
+    display: grid;
+}
+
+.account-summary-item small {
+    color: #825736;
+    font-size: 8px;
+    line-height: 1.05;
+    font-weight: 900;
+    white-space: nowrap;
+}
+
+.account-summary-item strong {
+    margin-top: 2px;
+    color: #0a56b6;
+    font-size: 16px;
+    line-height: 1;
+    font-weight: 1000;
+}
+
+.account-summary-item em {
+    margin-top: 4px;
+    color: #7a6e5e;
+    font-size: 7px;
+    line-height: 1;
+    font-style: normal;
+    white-space: nowrap;
+}
+
+.account-summary-note {
+    margin: 10px 0 0;
+    display: flex;
+    align-items: flex-start;
+    gap: 5px;
+    color: #6d7380;
+    font-size: 8px;
+    line-height: 1.3;
+}
+
+.account-summary-note span {
+    flex: 0 0 auto;
+    width: 13px;
+    height: 13px;
+    border: 1px solid #4c97df;
+    border-radius: 50%;
+    display: grid;
+    place-items: center;
+    color: #3389db;
+    font-weight: 900;
+}
+
+.account-package-card,
+.account-cancel-card,
+.account-language-card {
+    position: relative;
+    min-height: 70px;
+    padding: 11px 12px 10px 58px;
+}
+
+.account-package-card {
+    border-color: #d4e9c7;
+    background: rgba(241, 255, 235, .94);
+}
+
+.account-package-card small { color: #29933e; }
+.account-package-card strong {
+    display: block;
+    margin-top: 4px;
+    color: #113d78;
+    font-size: 16px;
+    line-height: 1;
+}
+
+.account-section-icon {
+    position: absolute;
+    left: 13px;
+    top: 50%;
+    width: 34px;
+    height: 34px;
+    transform: translateY(-50%);
+    border-radius: 9px;
+    display: grid;
+    place-items: center;
+    background: linear-gradient(#5cd95c, #1e9e2b);
+    color: #fff620;
+    font-size: 20px;
+    font-weight: 900;
+    box-shadow: inset 0 -3px 0 rgba(0,0,0,.16);
+}
+
+.account-cancel-card {
+    min-height: 94px;
+    padding-right: 94px;
+    border-color: #f6d4d2;
+    background: rgba(255, 241, 239, .95);
+}
+
+.account-cancel-card .account-section-icon {
+    background: linear-gradient(#ff7e72, #e43c35);
+    color: #fff;
+}
+
+.account-cancel-card small { color: #e43a35; }
+.account-cancel-card p { margin-top: 4px; }
+.account-cancel-card strong {
+    display: inline-block;
+    margin-top: 4px;
+    border: 1px solid #ffcbc8;
+    border-radius: 7px;
+    padding: 3px 12px;
+    background: #fff;
+    color: #ec3e37;
+    font-size: 13px;
+    line-height: 1;
+}
+
+.account-cancel-card > img {
+    position: absolute;
+    right: 8px;
+    bottom: 3px;
+    width: 84px;
+    height: 72px;
+    object-fit: contain;
+}
+
+.account-language-card {
+    min-height: 76px;
+    border-color: #ddd6ff;
+    background: rgba(246, 242, 255, .96);
+}
+
+.account-language-card .account-section-icon {
+    background: linear-gradient(#a86aff, #6d36dd);
+    color: #fff;
+}
+
+.account-language-card small {
+    margin-bottom: 6px;
+    color: #7544cb;
+}
+
+.account-language-options {
+    display: grid;
+    grid-template-columns: 1fr 1fr;
+    gap: 8px;
+}
+
+.account-language-option {
+    height: 32px;
+    min-width: 0;
+    border: 1px solid #d7dce8;
+    border-radius: 7px;
+    padding: 0 9px;
+    display: grid;
+    grid-template-columns: 22px 1fr 15px;
+    align-items: center;
+    gap: 4px;
+    background: #fff;
+    color: #47556d;
+    font-size: 9px;
+    text-align: left;
+}
+
+.account-language-option.active {
+    border: 2px solid #3190f4;
+}
+
+.account-language-option strong {
+    overflow: hidden;
+    text-overflow: ellipsis;
+}
+
+.account-language-option b {
+    display: none;
+    width: 14px;
+    height: 14px;
+    border-radius: 50%;
+    place-items: center;
+    background: #35bd4d;
+    color: #fff;
+    font-size: 9px;
+}
+
+.account-language-option.active b { display: grid; }
+
+.account-language-option > img {
+    width: 20px;
+    height: 14px;
+    border-radius: 2px;
+    object-fit: cover;
+    box-shadow: 0 0 0 1px rgba(26, 53, 91, .14);
+}
+
+.account-security-note {
+    min-height: 42px;
+    margin: 0;
+    padding: 8px 14px 8px 48px;
+    position: relative;
+    display: flex;
+    align-items: center;
+    color: #526783;
+    font-size: 8px;
+    line-height: 1.25;
+}
+
+.account-security-note > span {
+    position: absolute;
+    left: 14px;
+    width: 21px;
+    height: 21px;
+    border-radius: 50%;
+    display: grid;
+    place-items: center;
+    background: #3998e8;
+    color: #fff;
+    font-size: 15px;
+}
+
+@media (max-width: 390px) {
+    .account-menu-modal { padding: 6px; }
+    .account-menu-card {
+        width: calc(100vw - 12px);
+        max-height: calc(100dvh - 12px);
+        border-radius: 18px;
+        padding-left: 8px;
+        padding-right: 8px;
+    }
+    .account-menu-ribbon { font-size: 21px; }
+    .account-summary-item {
+        grid-template-columns: 29px minmax(0, 1fr);
+        padding-inline: 4px;
+    }
+    .account-summary-item > img {
+        width: 28px;
+        height: 28px;
+    }
+    .account-summary-item strong { font-size: 13px; }
+    .account-summary-item small { font-size: 7px; }
+    .account-cancel-card { padding-right: 75px; }
+    .account-cancel-card > img { width: 70px; }
+}
+
+
+/* Target Account artwork alignment (reference panel). */
+.account-menu-backdrop {
+    background: rgba(8, 39, 99, .18);
+    backdrop-filter: none;
+}
+
+.account-menu-card {
+    width: min(430px, calc(100vw - 28px));
+    padding: 58px 18px 14px;
+    border-color: #8fc8ef;
+    border-radius: 25px;
+    background: linear-gradient(180deg, #f7fcff 0%, #eef9ff 100%);
+    box-shadow:
+        inset 0 0 0 4px #fff,
+        0 10px 30px rgba(0, 54, 125, .34);
+}
+
+.account-menu-ribbon {
+    top: 16px;
+    width: min(258px, 65%);
+    height: 54px;
+    border-radius: 8px 8px 13px 13px;
+    z-index: 3;
+}
+
+.account-menu-ribbon::before,
+.account-menu-ribbon::after {
+    content: "";
+    position: absolute;
+    z-index: -1;
+    top: 9px;
+    width: 42px;
+    height: 39px;
+    border: 2px solid #075bc7;
+    background: linear-gradient(180deg, #299ef8, #0769dc 62%, #004eae);
+    filter: none;
+}
+
+.account-menu-ribbon::before {
+    left: -31px;
+    clip-path: polygon(0 15%, 100% 0, 100% 100%, 0 85%, 20% 50%);
+}
+
+.account-menu-ribbon::after {
+    right: -31px;
+    clip-path: polygon(0 0, 100% 15%, 80% 50%, 100% 85%, 0 100%);
+}
+
+.account-menu-ribbon > span::before,
+.account-menu-ribbon > span::after {
+    content: "🐾";
+    position: absolute;
+    top: 15px;
+    color: #0055ae;
+    font-size: 15px;
+    font-style: normal;
+    text-shadow: none;
+    opacity: .9;
+}
+
+.account-menu-ribbon > span::before { left: 22px; }
+.account-menu-ribbon > span::after { right: 22px; }
+
+.account-menu-close {
+    top: 13px;
+    right: 8px;
+    width: 43px;
+    height: 43px;
+}
+
+.account-menu-content {
+    gap: 9px;
+    padding: 8px 0 0;
+}
+
+.account-profile-card {
+    min-height: 112px;
+    grid-template-columns: 88px 1fr;
+    gap: 14px;
+    padding: 13px 16px;
+}
+
+.account-profile-avatar {
+    width: 84px;
+    height: 84px;
+    object-position: 50% 35%;
+}
+
+.account-profile-card strong {
+    font-size: 21px;
+}
+
+.account-summary-card {
+    min-height: 148px;
+    padding: 12px 13px 10px;
+}
+
+.account-summary-card h2 {
+    margin-bottom: 10px;
+    padding-bottom: 9px;
+    font-size: 13px;
+}
+
+.account-summary-grid {
+    min-height: 55px;
+}
+
+.account-summary-item {
+    grid-template-columns: 42px minmax(0, 1fr);
+    gap: 5px;
+    padding-inline: 9px;
+}
+
+.account-summary-item > img,
+.account-metric-icon {
+    width: 39px;
+    height: 39px;
+}
+
+.account-metric-icon {
+    border-radius: 50%;
+    display: grid;
+    place-items: center;
+    font-style: normal;
+    font-weight: 1000;
+    box-shadow:
+        inset 0 0 0 2px rgba(255,255,255,.72),
+        inset 0 -4px 0 rgba(0,0,0,.14),
+        0 2px 4px rgba(48, 68, 91, .2);
+}
+
+.account-metric-icon.is-data {
+    border: 2px solid #2d982c;
+    background: linear-gradient(#a7e94a, #45b62c);
+    color: #fff;
+    font-size: 10px;
+    text-shadow: 0 1px 0 #277f24;
+}
+
+.account-metric-icon.is-loyalty {
+    border: 2px solid #7f3bdd;
+    background: linear-gradient(#c98cff, #8147df);
+    color: #fff;
+    font-size: 20px;
+}
+
+.account-summary-item small {
+    font-size: 8px;
+}
+
+.account-summary-item strong {
+    font-size: 17px;
+}
+
+.account-summary-item em {
+    font-size: 7px;
+}
+
+.account-summary-note {
+    margin-top: 12px;
+    padding-inline: 11px;
+    font-size: 8px;
+}
+
+.account-package-card {
+    min-height: 79px;
+    padding: 13px 14px 11px 61px;
+}
+
+.account-package-card strong {
+    font-size: 17px;
+}
+
+.account-package-card .account-section-icon {
+    width: 38px;
+    height: 38px;
+    left: 14px;
+    border-radius: 8px;
+}
+
+.account-cancel-card {
+    min-height: 96px;
+    padding: 12px 122px 10px 61px;
+}
+
+.account-cancel-card .account-section-icon {
+    width: 38px;
+    height: 38px;
+    left: 14px;
+}
+
+.account-cancel-card > img {
+    right: 2px;
+    bottom: 0;
+    width: 121px;
+    height: 94px;
+    object-position: center bottom;
+}
+
+.account-language-card {
+    min-height: 77px;
+    padding: 11px 14px 10px 61px;
+}
+
+.account-language-card .account-section-icon {
+    width: 38px;
+    height: 38px;
+    left: 14px;
+}
+
+.account-security-note {
+    min-height: 45px;
+}
+
+.account-security-note::before,
+.account-security-note::after {
+    content: "🐾";
+    position: absolute;
+    bottom: -3px;
+    color: #cdeafa;
+    font-size: 22px;
+    opacity: .7;
+}
+
+.account-security-note::before { left: 2px; }
+.account-security-note::after { right: 2px; }
+
+@media (max-width: 390px) {
+    .account-menu-card {
+        width: calc(100vw - 14px);
+        padding: 56px 10px 10px;
+    }
+    .account-menu-ribbon {
+        width: 235px;
+        max-width: 64%;
+    }
+    .account-menu-ribbon::before { left: -25px; }
+    .account-menu-ribbon::after { right: -25px; }
+    .account-profile-card {
+        grid-template-columns: 76px 1fr;
+        min-height: 100px;
+        padding-inline: 12px;
+    }
+    .account-profile-avatar {
+        width: 72px;
+        height: 72px;
+    }
+    .account-summary-item {
+        grid-template-columns: 33px minmax(0, 1fr);
+        padding-inline: 4px;
+    }
+    .account-summary-item > img,
+    .account-metric-icon {
+        width: 31px;
+        height: 31px;
+    }
+    .account-metric-icon.is-data { font-size: 8px; }
+    .account-metric-icon.is-loyalty { font-size: 16px; }
+    .account-cancel-card {
+        padding-right: 98px;
+    }
+    .account-cancel-card > img {
+        width: 98px;
+    }
+}

+ 726 - 0
Kitty_Fall/Kitty_Fall/Kitty_Fall.Website/wwwroot/kitty/css/input.css

@@ -3926,3 +3926,729 @@ button {
         height: 46px;
     }
 }
+
+
+/* Account panel opened from the hamburger menu. */
+body.has-account-menu {
+    overflow: hidden;
+}
+
+.account-menu-modal {
+    position: fixed;
+    z-index: 10000;
+    inset: 0;
+    display: none;
+    place-items: center;
+    padding: 12px;
+}
+
+.account-menu-modal.is-open {
+    display: grid;
+}
+
+.account-menu-backdrop {
+    position: absolute;
+    inset: 0;
+    border: 0;
+    background: rgba(8, 39, 99, 0.52);
+    backdrop-filter: blur(2px);
+}
+
+.account-menu-card {
+    position: relative;
+    width: min(430px, calc(100vw - 24px));
+    max-height: calc(100dvh - 24px);
+    border: 3px solid #9bcdf5;
+    border-radius: 24px;
+    padding: 46px 12px 12px;
+    background:
+        linear-gradient(180deg, rgba(255,255,255,.94), rgba(239,249,255,.97)),
+        url("../assets/kitty-fall/bg.png") center/cover;
+    box-shadow:
+        inset 0 0 0 3px #fff,
+        0 12px 35px rgba(0, 54, 125, .38);
+    overflow-y: auto;
+    overscroll-behavior: contain;
+    color: #24375c;
+}
+
+.account-menu-card::-webkit-scrollbar {
+    width: 0;
+}
+
+.account-menu-ribbon {
+    position: absolute;
+    z-index: 2;
+    top: 15px;
+    left: 50%;
+    width: min(270px, 68%);
+    height: 54px;
+    transform: translateX(-50%);
+    display: grid;
+    place-items: center;
+    border: 2px solid #075bc7;
+    border-radius: 9px 9px 16px 16px;
+    background: linear-gradient(180deg, #29a0ff 0%, #0874e8 54%, #0053c5 100%);
+    color: #fff;
+    font-size: 25px;
+    line-height: 1;
+    font-weight: 1000;
+    font-style: italic;
+    text-shadow: 0 2px 0 #0759af;
+    box-shadow: 0 5px 0 rgba(0, 66, 167, .25);
+}
+
+.account-menu-ribbon::before,
+.account-menu-ribbon::after {
+    content: "🐾";
+    position: absolute;
+    top: 13px;
+    font-size: 20px;
+    filter: saturate(.8);
+}
+
+.account-menu-ribbon::before { left: 14px; }
+.account-menu-ribbon::after { right: 14px; }
+
+.account-menu-close {
+    position: absolute;
+    z-index: 4;
+    top: 12px;
+    right: 9px;
+    width: 42px;
+    height: 42px;
+    border: 2px solid #075bc7;
+    border-radius: 50%;
+    display: grid;
+    place-items: center;
+    padding: 0 0 3px;
+    background: linear-gradient(#42b1ff, #0876e6);
+    color: #fff;
+    font-size: 34px;
+    line-height: 1;
+    font-weight: 900;
+    box-shadow: 0 3px 0 rgba(0, 58, 154, .25);
+}
+
+.account-menu-content {
+    display: grid;
+    gap: 8px;
+    padding: 8px 2px 0;
+}
+
+.account-profile-card,
+.account-summary-card,
+.account-package-card,
+.account-cancel-card,
+.account-language-card,
+.account-security-note {
+    border: 1px solid #cfe3f8;
+    border-radius: 13px;
+    background: rgba(238, 248, 255, .92);
+}
+
+.account-profile-card {
+    min-height: 104px;
+    padding: 13px 14px;
+    display: grid;
+    grid-template-columns: 82px 1fr;
+    align-items: center;
+    gap: 12px;
+}
+
+.account-profile-avatar {
+    width: 76px;
+    height: 76px;
+    border: 2px solid #4ca7ee;
+    border-radius: 50%;
+    object-fit: cover;
+    background: #fff;
+    box-shadow: 0 3px 8px rgba(32, 104, 172, .2);
+}
+
+.account-profile-card small,
+.account-package-card small,
+.account-cancel-card small,
+.account-language-card small {
+    display: block;
+    color: #49617d;
+    font-size: 10px;
+    line-height: 1.2;
+    font-weight: 900;
+}
+
+.account-profile-card strong {
+    display: block;
+    margin-top: 5px;
+    color: #142c60;
+    font-size: 20px;
+    line-height: 1;
+    font-weight: 1000;
+}
+
+.account-profile-card p,
+.account-package-card p,
+.account-cancel-card p {
+    margin: 7px 0 0;
+    color: #52647c;
+    font-size: 10px;
+    line-height: 1.25;
+}
+
+.account-summary-card {
+    padding: 10px 12px 9px;
+    border-color: #f2ddae;
+    background: rgba(255, 251, 231, .94);
+}
+
+.account-summary-card h2 {
+    margin: 0 0 8px;
+    padding-bottom: 7px;
+    border-bottom: 1px solid #f1e2be;
+    color: #934f24;
+    font-size: 12px;
+    line-height: 1;
+    font-weight: 1000;
+}
+
+.account-summary-card h2 span {
+    color: #ffb21a;
+    margin-right: 5px;
+}
+
+.account-summary-grid {
+    display: grid;
+    grid-template-columns: repeat(3, minmax(0, 1fr));
+}
+
+.account-summary-item {
+    min-width: 0;
+    display: grid;
+    grid-template-columns: 36px minmax(0, 1fr);
+    align-items: center;
+    gap: 4px;
+    padding: 2px 7px;
+    border-right: 1px solid #eadfca;
+}
+
+.account-summary-item:first-child { padding-left: 0; }
+.account-summary-item:last-child {
+    padding-right: 0;
+    border-right: 0;
+}
+
+.account-summary-item > img {
+    width: 34px;
+    height: 34px;
+    object-fit: contain;
+}
+
+.account-summary-item span {
+    min-width: 0;
+    display: grid;
+}
+
+.account-summary-item small {
+    color: #825736;
+    font-size: 8px;
+    line-height: 1.05;
+    font-weight: 900;
+    white-space: nowrap;
+}
+
+.account-summary-item strong {
+    margin-top: 2px;
+    color: #0a56b6;
+    font-size: 16px;
+    line-height: 1;
+    font-weight: 1000;
+}
+
+.account-summary-item em {
+    margin-top: 4px;
+    color: #7a6e5e;
+    font-size: 7px;
+    line-height: 1;
+    font-style: normal;
+    white-space: nowrap;
+}
+
+.account-summary-note {
+    margin: 10px 0 0;
+    display: flex;
+    align-items: flex-start;
+    gap: 5px;
+    color: #6d7380;
+    font-size: 8px;
+    line-height: 1.3;
+}
+
+.account-summary-note span {
+    flex: 0 0 auto;
+    width: 13px;
+    height: 13px;
+    border: 1px solid #4c97df;
+    border-radius: 50%;
+    display: grid;
+    place-items: center;
+    color: #3389db;
+    font-weight: 900;
+}
+
+.account-package-card,
+.account-cancel-card,
+.account-language-card {
+    position: relative;
+    min-height: 70px;
+    padding: 11px 12px 10px 58px;
+}
+
+.account-package-card {
+    border-color: #d4e9c7;
+    background: rgba(241, 255, 235, .94);
+}
+
+.account-package-card small { color: #29933e; }
+.account-package-card strong {
+    display: block;
+    margin-top: 4px;
+    color: #113d78;
+    font-size: 16px;
+    line-height: 1;
+}
+
+.account-section-icon {
+    position: absolute;
+    left: 13px;
+    top: 50%;
+    width: 34px;
+    height: 34px;
+    transform: translateY(-50%);
+    border-radius: 9px;
+    display: grid;
+    place-items: center;
+    background: linear-gradient(#5cd95c, #1e9e2b);
+    color: #fff620;
+    font-size: 20px;
+    font-weight: 900;
+    box-shadow: inset 0 -3px 0 rgba(0,0,0,.16);
+}
+
+.account-cancel-card {
+    min-height: 94px;
+    padding-right: 94px;
+    border-color: #f6d4d2;
+    background: rgba(255, 241, 239, .95);
+}
+
+.account-cancel-card .account-section-icon {
+    background: linear-gradient(#ff7e72, #e43c35);
+    color: #fff;
+}
+
+.account-cancel-card small { color: #e43a35; }
+.account-cancel-card p { margin-top: 4px; }
+.account-cancel-card strong {
+    display: inline-block;
+    margin-top: 4px;
+    border: 1px solid #ffcbc8;
+    border-radius: 7px;
+    padding: 3px 12px;
+    background: #fff;
+    color: #ec3e37;
+    font-size: 13px;
+    line-height: 1;
+}
+
+.account-cancel-card > img {
+    position: absolute;
+    right: 8px;
+    bottom: 3px;
+    width: 84px;
+    height: 72px;
+    object-fit: contain;
+}
+
+.account-language-card {
+    min-height: 76px;
+    border-color: #ddd6ff;
+    background: rgba(246, 242, 255, .96);
+}
+
+.account-language-card .account-section-icon {
+    background: linear-gradient(#a86aff, #6d36dd);
+    color: #fff;
+}
+
+.account-language-card small {
+    margin-bottom: 6px;
+    color: #7544cb;
+}
+
+.account-language-options {
+    display: grid;
+    grid-template-columns: 1fr 1fr;
+    gap: 8px;
+}
+
+.account-language-option {
+    height: 32px;
+    min-width: 0;
+    border: 1px solid #d7dce8;
+    border-radius: 7px;
+    padding: 0 9px;
+    display: grid;
+    grid-template-columns: 22px 1fr 15px;
+    align-items: center;
+    gap: 4px;
+    background: #fff;
+    color: #47556d;
+    font-size: 9px;
+    text-align: left;
+}
+
+.account-language-option.active {
+    border: 2px solid #3190f4;
+}
+
+.account-language-option strong {
+    overflow: hidden;
+    text-overflow: ellipsis;
+}
+
+.account-language-option b {
+    display: none;
+    width: 14px;
+    height: 14px;
+    border-radius: 50%;
+    place-items: center;
+    background: #35bd4d;
+    color: #fff;
+    font-size: 9px;
+}
+
+.account-language-option.active b { display: grid; }
+
+.account-language-option > img {
+    width: 20px;
+    height: 14px;
+    border-radius: 2px;
+    object-fit: cover;
+    box-shadow: 0 0 0 1px rgba(26, 53, 91, .14);
+}
+
+.account-security-note {
+    min-height: 42px;
+    margin: 0;
+    padding: 8px 14px 8px 48px;
+    position: relative;
+    display: flex;
+    align-items: center;
+    color: #526783;
+    font-size: 8px;
+    line-height: 1.25;
+}
+
+.account-security-note > span {
+    position: absolute;
+    left: 14px;
+    width: 21px;
+    height: 21px;
+    border-radius: 50%;
+    display: grid;
+    place-items: center;
+    background: #3998e8;
+    color: #fff;
+    font-size: 15px;
+}
+
+@media (max-width: 390px) {
+    .account-menu-modal { padding: 6px; }
+    .account-menu-card {
+        width: calc(100vw - 12px);
+        max-height: calc(100dvh - 12px);
+        border-radius: 18px;
+        padding-left: 8px;
+        padding-right: 8px;
+    }
+    .account-menu-ribbon { font-size: 21px; }
+    .account-summary-item {
+        grid-template-columns: 29px minmax(0, 1fr);
+        padding-inline: 4px;
+    }
+    .account-summary-item > img {
+        width: 28px;
+        height: 28px;
+    }
+    .account-summary-item strong { font-size: 13px; }
+    .account-summary-item small { font-size: 7px; }
+    .account-cancel-card { padding-right: 75px; }
+    .account-cancel-card > img { width: 70px; }
+}
+
+
+/* Target Account artwork alignment (reference panel). */
+.account-menu-backdrop {
+    background: rgba(8, 39, 99, .18);
+    backdrop-filter: none;
+}
+
+.account-menu-card {
+    width: min(430px, calc(100vw - 28px));
+    padding: 58px 18px 14px;
+    border-color: #8fc8ef;
+    border-radius: 25px;
+    background: linear-gradient(180deg, #f7fcff 0%, #eef9ff 100%);
+    box-shadow:
+        inset 0 0 0 4px #fff,
+        0 10px 30px rgba(0, 54, 125, .34);
+}
+
+.account-menu-ribbon {
+    top: 16px;
+    width: min(258px, 65%);
+    height: 54px;
+    border-radius: 8px 8px 13px 13px;
+    z-index: 3;
+}
+
+.account-menu-ribbon::before,
+.account-menu-ribbon::after {
+    content: "";
+    position: absolute;
+    z-index: -1;
+    top: 9px;
+    width: 42px;
+    height: 39px;
+    border: 2px solid #075bc7;
+    background: linear-gradient(180deg, #299ef8, #0769dc 62%, #004eae);
+    filter: none;
+}
+
+.account-menu-ribbon::before {
+    left: -31px;
+    clip-path: polygon(0 15%, 100% 0, 100% 100%, 0 85%, 20% 50%);
+}
+
+.account-menu-ribbon::after {
+    right: -31px;
+    clip-path: polygon(0 0, 100% 15%, 80% 50%, 100% 85%, 0 100%);
+}
+
+.account-menu-ribbon > span::before,
+.account-menu-ribbon > span::after {
+    content: "🐾";
+    position: absolute;
+    top: 15px;
+    color: #0055ae;
+    font-size: 15px;
+    font-style: normal;
+    text-shadow: none;
+    opacity: .9;
+}
+
+.account-menu-ribbon > span::before { left: 22px; }
+.account-menu-ribbon > span::after { right: 22px; }
+
+.account-menu-close {
+    top: 13px;
+    right: 8px;
+    width: 43px;
+    height: 43px;
+}
+
+.account-menu-content {
+    gap: 9px;
+    padding: 8px 0 0;
+}
+
+.account-profile-card {
+    min-height: 112px;
+    grid-template-columns: 88px 1fr;
+    gap: 14px;
+    padding: 13px 16px;
+}
+
+.account-profile-avatar {
+    width: 84px;
+    height: 84px;
+    object-position: 50% 35%;
+}
+
+.account-profile-card strong {
+    font-size: 21px;
+}
+
+.account-summary-card {
+    min-height: 148px;
+    padding: 12px 13px 10px;
+}
+
+.account-summary-card h2 {
+    margin-bottom: 10px;
+    padding-bottom: 9px;
+    font-size: 13px;
+}
+
+.account-summary-grid {
+    min-height: 55px;
+}
+
+.account-summary-item {
+    grid-template-columns: 42px minmax(0, 1fr);
+    gap: 5px;
+    padding-inline: 9px;
+}
+
+.account-summary-item > img,
+.account-metric-icon {
+    width: 39px;
+    height: 39px;
+}
+
+.account-metric-icon {
+    border-radius: 50%;
+    display: grid;
+    place-items: center;
+    font-style: normal;
+    font-weight: 1000;
+    box-shadow:
+        inset 0 0 0 2px rgba(255,255,255,.72),
+        inset 0 -4px 0 rgba(0,0,0,.14),
+        0 2px 4px rgba(48, 68, 91, .2);
+}
+
+.account-metric-icon.is-data {
+    border: 2px solid #2d982c;
+    background: linear-gradient(#a7e94a, #45b62c);
+    color: #fff;
+    font-size: 10px;
+    text-shadow: 0 1px 0 #277f24;
+}
+
+.account-metric-icon.is-loyalty {
+    border: 2px solid #7f3bdd;
+    background: linear-gradient(#c98cff, #8147df);
+    color: #fff;
+    font-size: 20px;
+}
+
+.account-summary-item small {
+    font-size: 8px;
+}
+
+.account-summary-item strong {
+    font-size: 17px;
+}
+
+.account-summary-item em {
+    font-size: 7px;
+}
+
+.account-summary-note {
+    margin-top: 12px;
+    padding-inline: 11px;
+    font-size: 8px;
+}
+
+.account-package-card {
+    min-height: 79px;
+    padding: 13px 14px 11px 61px;
+}
+
+.account-package-card strong {
+    font-size: 17px;
+}
+
+.account-package-card .account-section-icon {
+    width: 38px;
+    height: 38px;
+    left: 14px;
+    border-radius: 8px;
+}
+
+.account-cancel-card {
+    min-height: 96px;
+    padding: 12px 122px 10px 61px;
+}
+
+.account-cancel-card .account-section-icon {
+    width: 38px;
+    height: 38px;
+    left: 14px;
+}
+
+.account-cancel-card > img {
+    right: 2px;
+    bottom: 0;
+    width: 121px;
+    height: 94px;
+    object-position: center bottom;
+}
+
+.account-language-card {
+    min-height: 77px;
+    padding: 11px 14px 10px 61px;
+}
+
+.account-language-card .account-section-icon {
+    width: 38px;
+    height: 38px;
+    left: 14px;
+}
+
+.account-security-note {
+    min-height: 45px;
+}
+
+.account-security-note::before,
+.account-security-note::after {
+    content: "🐾";
+    position: absolute;
+    bottom: -3px;
+    color: #cdeafa;
+    font-size: 22px;
+    opacity: .7;
+}
+
+.account-security-note::before { left: 2px; }
+.account-security-note::after { right: 2px; }
+
+@media (max-width: 390px) {
+    .account-menu-card {
+        width: calc(100vw - 14px);
+        padding: 56px 10px 10px;
+    }
+    .account-menu-ribbon {
+        width: 235px;
+        max-width: 64%;
+    }
+    .account-menu-ribbon::before { left: -25px; }
+    .account-menu-ribbon::after { right: -25px; }
+    .account-profile-card {
+        grid-template-columns: 76px 1fr;
+        min-height: 100px;
+        padding-inline: 12px;
+    }
+    .account-profile-avatar {
+        width: 72px;
+        height: 72px;
+    }
+    .account-summary-item {
+        grid-template-columns: 33px minmax(0, 1fr);
+        padding-inline: 4px;
+    }
+    .account-summary-item > img,
+    .account-metric-icon {
+        width: 31px;
+        height: 31px;
+    }
+    .account-metric-icon.is-data { font-size: 8px; }
+    .account-metric-icon.is-loyalty { font-size: 16px; }
+    .account-cancel-card {
+        padding-right: 98px;
+    }
+    .account-cancel-card > img {
+        width: 98px;
+    }
+}

+ 72 - 0
Kitty_Fall/Kitty_Fall/sql/20260713_audit_game_reward_type.sql

@@ -0,0 +1,72 @@
+-- Chi kiem tra, khong cap nhat du lieu.
+
+-- 1. Tim reward log bi khac loai so voi ket qua random da luu tren attempt.
+SELECT
+    a.ATTEMPT_ID,
+    a.MSISDN,
+    a.ROOT_TRANS_ID,
+    a.CHILD_TRANS_ID,
+    a.MODE_ID,
+    a.LEVEL_NO,
+    a.REWARD_OPTION_ID,
+    a.REWARD_TYPE AS RANDOM_REWARD_TYPE,
+    a.REWARD_VALUE AS RANDOM_REWARD_VALUE,
+    a.REWARD_UNIT AS RANDOM_REWARD_UNIT,
+    r.REWARD_LOG_ID,
+    r.REWARD_TYPE AS LOG_REWARD_TYPE,
+    r.REWARD_VALUE AS LOG_REWARD_VALUE,
+    r.REWARD_UNIT AS LOG_REWARD_UNIT,
+    r.STATUS AS LOG_STATUS,
+    r.RESPONSE_CODE,
+    r.RESPONSE_MESSAGE,
+    r.CREATED_TIME,
+    r.UPDATED_TIME
+FROM GAME_LEVEL_ATTEMPT a
+JOIN GAME_REWARD_LOG r ON r.ATTEMPT_ID = a.ATTEMPT_ID
+WHERE a.REWARD_TYPE IS NOT NULL
+  AND (
+      UPPER(TRIM(a.REWARD_TYPE)) <> UPPER(TRIM(r.REWARD_TYPE))
+      OR NVL(a.REWARD_VALUE, -1) <> NVL(r.REWARD_VALUE, -1)
+      OR NVL(UPPER(TRIM(a.REWARD_UNIT)), '-') <> NVL(UPPER(TRIM(r.REWARD_UNIT)), '-')
+  )
+ORDER BY r.CREATED_TIME DESC, r.REWARD_LOG_ID DESC;
+
+-- 2. Tim attempt khac loai so voi option DB da duoc random.
+SELECT
+    a.ATTEMPT_ID,
+    a.MSISDN,
+    a.ROOT_TRANS_ID,
+    a.LEVEL_NO,
+    a.REWARD_OPTION_ID,
+    a.REWARD_TYPE AS RANDOM_REWARD_TYPE,
+    o.REWARD_TYPE AS CONFIG_REWARD_TYPE,
+    a.REWARD_VALUE AS RANDOM_REWARD_VALUE,
+    o.REWARD_VALUE AS CONFIG_REWARD_VALUE,
+    a.CREATED_TIME
+FROM GAME_LEVEL_ATTEMPT a
+JOIN GAME_LEVEL_REWARD_OPTION o ON o.REWARD_OPTION_ID = a.REWARD_OPTION_ID
+WHERE UPPER(TRIM(a.REWARD_TYPE)) <> UPPER(TRIM(o.REWARD_TYPE))
+   OR NVL(a.REWARD_VALUE, -1) <> NVL(o.REWARD_VALUE, -1)
+   OR NVL(UPPER(TRIM(a.REWARD_UNIT)), '-') <> NVL(UPPER(TRIM(o.REWARD_UNIT)), '-')
+ORDER BY a.CREATED_TIME DESC, a.ATTEMPT_ID DESC;
+
+-- 3. Tim cau hinh han muc dang cho phep doi cheo loai reward.
+SELECT
+    LIMIT_CONFIG_ID,
+    LIMIT_CODE,
+    LIMIT_NAME,
+    REWARD_TYPE,
+    REWARD_UNIT,
+    EXCEED_ACTION,
+    FALLBACK_REWARD_TYPE,
+    FALLBACK_REWARD_VALUE,
+    FALLBACK_REWARD_UNIT,
+    EFFECTIVE_FROM,
+    EFFECTIVE_TO,
+    STATUS
+FROM GAME_REWARD_LIMIT_CONFIG
+WHERE STATUS = 1
+  AND UPPER(TRIM(EXCEED_ACTION)) = 'FALLBACK'
+  AND FALLBACK_REWARD_TYPE IS NOT NULL
+  AND UPPER(TRIM(REWARD_TYPE)) <> UPPER(TRIM(FALLBACK_REWARD_TYPE))
+ORDER BY PRIORITY DESC, LIMIT_CONFIG_ID;

+ 91 - 0
Kitty_Fall/Kitty_Fall/sql/20260713_monthly_winner_one_user_per_prize.sql

@@ -0,0 +1,91 @@
+-- Nang cap online:
+-- 1. So winner cua moi giai lay theo GAME_MONTHLY_PRIZE_CONFIG.QUANTITY.
+-- 2. Mot user chi duoc trung mot giai trong cung mot ky quay.
+-- Chay thu cong bang user schema dang so huu cac bang GAME_*.
+
+WHENEVER SQLERROR EXIT SQL.SQLCODE ROLLBACK;
+
+-- Dung ngay neu du lieu hien tai co mot user trung nhieu hon mot giai trong cung draw.
+DECLARE
+    V_DUPLICATE_USER NUMBER;
+BEGIN
+    SELECT COUNT(*)
+      INTO V_DUPLICATE_USER
+      FROM (
+          SELECT DRAW_ID, MSISDN
+            FROM GAME_MONTHLY_WINNER
+           GROUP BY DRAW_ID, MSISDN
+          HAVING COUNT(*) > 1
+      );
+
+    IF V_DUPLICATE_USER > 0 THEN
+        RAISE_APPLICATION_ERROR(
+            -20001,
+            'GAME_MONTHLY_WINNER co user trung nhieu giai trong cung draw. duplicateUser=' ||
+            V_DUPLICATE_USER || '. Can doi soat du lieu truoc khi nang cap.'
+        );
+    END IF;
+END;
+/
+
+-- Bo constraint sai neu da tung chay ban SQL gioi han mot winner cho moi PrizeCode.
+DECLARE
+    V_COUNT NUMBER;
+BEGIN
+    SELECT COUNT(*)
+      INTO V_COUNT
+      FROM USER_CONSTRAINTS
+     WHERE TABLE_NAME = 'GAME_MONTHLY_WINNER'
+       AND CONSTRAINT_NAME = 'UK_GMW_DRAW_PRIZE';
+
+    IF V_COUNT > 0 THEN
+        EXECUTE IMMEDIATE
+            'ALTER TABLE GAME_MONTHLY_WINNER DROP CONSTRAINT UK_GMW_DRAW_PRIZE';
+    END IF;
+END;
+/
+
+-- Giu rang buoc cu: khong ghi trung cung user vao cung PrizeCode trong mot draw.
+DECLARE
+    V_COUNT NUMBER;
+BEGIN
+    SELECT COUNT(*)
+      INTO V_COUNT
+      FROM USER_CONSTRAINTS
+     WHERE TABLE_NAME = 'GAME_MONTHLY_WINNER'
+       AND CONSTRAINT_NAME = 'UK_GMW_DRAW_PRIZE_MSISDN';
+
+    IF V_COUNT = 0 THEN
+        EXECUTE IMMEDIATE
+            'ALTER TABLE GAME_MONTHLY_WINNER ADD CONSTRAINT UK_GMW_DRAW_PRIZE_MSISDN UNIQUE (DRAW_ID, PRIZE_CODE, MSISDN)';
+    END IF;
+END;
+/
+
+-- Khoa cung o DB: mot user khong duoc trung hai giai trong cung draw.
+DECLARE
+    V_COUNT NUMBER;
+BEGIN
+    SELECT COUNT(*)
+      INTO V_COUNT
+      FROM USER_CONSTRAINTS
+     WHERE TABLE_NAME = 'GAME_MONTHLY_WINNER'
+       AND CONSTRAINT_NAME = 'UK_GMW_DRAW_MSISDN';
+
+    IF V_COUNT = 0 THEN
+        EXECUTE IMMEDIATE
+            'ALTER TABLE GAME_MONTHLY_WINNER ADD CONSTRAINT UK_GMW_DRAW_MSISDN UNIQUE (DRAW_ID, MSISDN)';
+    END IF;
+END;
+/
+
+-- Kiem tra cau hinh so winner va constraint sau nang cap.
+SELECT PRIZE_CODE, QUANTITY, STATUS
+  FROM GAME_MONTHLY_PRIZE_CONFIG
+ ORDER BY DISPLAY_ORDER, PRIZE_CONFIG_ID;
+
+SELECT CONSTRAINT_NAME, STATUS
+  FROM USER_CONSTRAINTS
+ WHERE TABLE_NAME = 'GAME_MONTHLY_WINNER'
+   AND CONSTRAINT_NAME IN ('UK_GMW_DRAW_PRIZE_MSISDN', 'UK_GMW_DRAW_MSISDN')
+ ORDER BY CONSTRAINT_NAME;