feat: enhance layout and styling across components for improved responsiveness and user experience
Gitea Auto Deploy / Deploy-Container (push) Successful in 35s
Gitea Auto Deploy / Deploy-Container (push) Successful in 35s
This commit is contained in:
+7
-4
@@ -11,10 +11,13 @@
|
||||
nocompatible: true;
|
||||
}
|
||||
|
||||
:root {
|
||||
--size-big: 4vw;
|
||||
--size-medium: 3vw;
|
||||
--size-small: 2vw;
|
||||
html, body {
|
||||
height: 100%;
|
||||
height: 100dvh;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
overscroll-behavior: none;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
/* Smooth Modern Ultra-Sleek Scrollbars */
|
||||
|
||||
+4
-4
@@ -64,9 +64,9 @@ export default async function RootLayout({
|
||||
const locale = await getLocale()
|
||||
|
||||
return (
|
||||
<html lang={locale} suppressHydrationWarning className="h-full overflow-hidden">
|
||||
<html lang={locale} suppressHydrationWarning className="h-full w-full overflow-hidden overscroll-none fixed inset-0">
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased h-full overflow-hidden max-w-full`}
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased h-full w-full overflow-hidden overscroll-none fixed inset-0 max-w-full`}
|
||||
suppressHydrationWarning
|
||||
>
|
||||
<NextIntlClientProvider messages={messages}>
|
||||
@@ -74,7 +74,7 @@ export default async function RootLayout({
|
||||
<ThemeProvider>
|
||||
<ClientThemeWrapper>
|
||||
<ClientDataFetcher >
|
||||
<div className="h-screen w-full flex flex-col overflow-hidden bg-ambient-mesh selection:bg-primary/20 selection:text-primary">
|
||||
<div className="h-dvh w-full flex flex-col overflow-hidden bg-ambient-mesh selection:bg-primary/20 selection:text-primary">
|
||||
<Header />
|
||||
<main className="grid grid-cols-12 w-full flex-1 min-h-0 overflow-hidden">
|
||||
<aside className="hidden sm:block sm:col-span-4 md:col-span-4 lg:col-span-3 h-full overflow-hidden border-r border-base-content/10 bg-base-100/30 backdrop-blur-sm">
|
||||
@@ -84,7 +84,7 @@ export default async function RootLayout({
|
||||
<div className="shrink-0">
|
||||
<ActionBar />
|
||||
</div>
|
||||
<div className="flex-1 overflow-y-auto min-h-0 p-2 sm:p-3 sm:pt-1 flex flex-col justify-between">
|
||||
<div className="flex-1 overflow-y-auto min-h-0 px-3 sm:px-4 pt-1 pb-3 flex flex-col justify-between">
|
||||
<div>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@@ -122,8 +122,18 @@ export default function ActionBar() {
|
||||
};
|
||||
|
||||
const actionMove = (path: string) => {
|
||||
router.push(`/${path}`)
|
||||
}
|
||||
if (typeof document !== "undefined" && document.activeElement instanceof HTMLElement) {
|
||||
document.activeElement.blur();
|
||||
}
|
||||
if (typeof window !== "undefined") {
|
||||
window.scrollTo(0, 0);
|
||||
const scrollContainer = document.querySelector('section > div.flex-1.overflow-y-auto');
|
||||
if (scrollContainer) {
|
||||
scrollContainer.scrollTo({ top: 0, behavior: 'instant' });
|
||||
}
|
||||
}
|
||||
router.push(`/${path}`, { scroll: false });
|
||||
};
|
||||
|
||||
const handleProfileSelect = (profileId: number) => {
|
||||
if (!avatarSelected || !avatars[avatarSelected.ID]) return;
|
||||
@@ -267,7 +277,7 @@ export default function ActionBar() {
|
||||
const currentActivePath = pathname?.replace(/^\//, '') || "";
|
||||
|
||||
return (
|
||||
<div className="w-full bg-transparent px-3 sm:px-4 py-2 space-y-2">
|
||||
<div className="w-full bg-transparent px-3 sm:px-4 pt-2.5 sm:pt-3 pb-1 space-y-2">
|
||||
{/* Top Row: Character Identity + Profile Selector + Connect PS */}
|
||||
<div className="flex flex-wrap items-center justify-between gap-2.5">
|
||||
{/* Character Identity */}
|
||||
@@ -436,9 +446,9 @@ export default function ActionBar() {
|
||||
</button>
|
||||
|
||||
<div className="dropdown dropdown-end w-full">
|
||||
<label tabIndex={0} className="btn btn-info btn-sm w-full font-bold shadow-xs">
|
||||
<div tabIndex={0} role="button" className="btn btn-info btn-sm w-full font-bold shadow-xs">
|
||||
{transI18n("actions")}
|
||||
</label>
|
||||
</div>
|
||||
<ul
|
||||
tabIndex={0}
|
||||
className="dropdown-content z-50 menu p-2 shadow-2xl bg-base-100 rounded-2xl w-56 mt-2 border border-base-300 space-y-1"
|
||||
@@ -554,7 +564,7 @@ export default function ActionBar() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleConfirmDeleteProfile}
|
||||
className="flex-1 h-10 px-4 rounded-xl font-bold text-xs sm:text-sm bg-gradient-to-r from-rose-500 to-red-600 hover:from-rose-400 hover:to-red-500 text-white shadow-md shadow-rose-500/25 hover:shadow-rose-500/40 transition-all flex items-center justify-center gap-1.5 cursor-pointer"
|
||||
className="flex-1 h-10 px-4 rounded-xl font-bold text-xs sm:text-sm bg-linear-to-r from-rose-500 to-red-600 hover:from-rose-400 hover:to-red-500 text-white shadow-md shadow-rose-500/25 hover:shadow-rose-500/40 transition-all flex items-center justify-center gap-1.5 cursor-pointer"
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
<span>{transI18n("confirmDelete")}</span>
|
||||
|
||||
@@ -456,7 +456,7 @@ export default function AvatarInfo() {
|
||||
<dialog id="action_detail_modal" className="modal">
|
||||
<div className="modal-box w-11/12 max-w-[92vw] sm:max-w-[88vw] max-h-[90vh] flex flex-col bg-base-100 text-base-content border border-white/10 shadow-2xl rounded-2xl p-0 overflow-hidden">
|
||||
<div className="sticky top-0 z-20 flex items-center justify-between px-6 py-4 border-b border-base-content/10 bg-base-100 shrink-0">
|
||||
<h3 className="font-bold text-xl tracking-tight bg-clip-text text-transparent bg-linear-to-r from-primary via-accent to-secondary">
|
||||
<h3 className="font-bold text-xl tracking-tight text-base-content">
|
||||
{transI18n("lightconeEquipment")}
|
||||
</h3>
|
||||
<button
|
||||
|
||||
@@ -19,7 +19,7 @@ export default function LightconeCard({ data }: LightconeCardProps) {
|
||||
const is4Star = data.Rarity === "CombatPowerLightconeRarity4";
|
||||
|
||||
return (
|
||||
<li className="group relative flex flex-col items-center rounded-xl overflow-hidden bg-base-100 border border-base-content/10 shadow-sm hover:shadow-md cursor-pointer select-none h-full">
|
||||
<li className="group relative flex flex-col items-center rounded-xl overflow-hidden bg-base-100 border border-base-content/10 shadow-xs hover:shadow-md hover:border-primary/50 transition-all duration-200 cursor-pointer select-none w-full list-none">
|
||||
{/* Artwork container with vibrant rarity background */}
|
||||
<div
|
||||
className={`w-full relative aspect-3/4 flex items-center justify-center overflow-hidden bg-linear-to-b ${
|
||||
@@ -42,12 +42,12 @@ export default function LightconeCard({ data }: LightconeCardProps) {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Name Label - Uniform Fixed-Height Footer */}
|
||||
<div className="w-full h-11 px-1 py-0.5 text-center flex items-center justify-center bg-base-100">
|
||||
{/* Name Label - Uniform Fixed-Height Footer with larger text */}
|
||||
<div className="w-full min-h-12 px-1.5 py-1 text-center flex items-center justify-center bg-base-100 shrink-0">
|
||||
<ParseText
|
||||
locale={locale}
|
||||
text={text}
|
||||
className="font-bold text-xs leading-tight line-clamp-2 text-base-content group-hover:text-primary transition-colors text-center"
|
||||
className="font-bold text-xs sm:text-[13px] leading-snug line-clamp-2 text-base-content group-hover:text-primary transition-colors text-center"
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@@ -27,7 +27,7 @@ const getRarityColor = (rarity: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
const slotIconMap: Record<string, { icon: string; key: string }> = {
|
||||
export const slotIconMap: Record<string, { icon: string; key: string }> = {
|
||||
'1': { icon: '/relics/HEAD.png', key: 'HEAD' },
|
||||
'2': { icon: '/relics/HAND.png', key: 'HAND' },
|
||||
'3': { icon: '/relics/BODY.png', key: 'BODY' },
|
||||
|
||||
@@ -280,7 +280,7 @@ export default function Header() {
|
||||
};
|
||||
|
||||
return (
|
||||
<header className="sticky top-0 z-40 w-full bg-base-100 border-b border-base-300 shadow-xs">
|
||||
<header className="sticky top-0 z-40 w-full shrink-0 bg-base-100 border-b border-base-300 shadow-xs">
|
||||
<div className="navbar max-w-[1920px] mx-auto px-2 sm:px-4 py-1.5 min-h-14">
|
||||
<div className="navbar-start gap-2">
|
||||
{/* Mobile menu dropdown */}
|
||||
@@ -638,7 +638,7 @@ export default function Header() {
|
||||
<dialog key={id} id={id} className="modal">
|
||||
<div className={`modal-box w-11/12 ${
|
||||
id === 'monster_modal' || id === 'import_modal' || id === 'extra_modal'
|
||||
? 'max-w-[95vw] sm:max-w-[90vw] lg:max-w-[85vw] 2xl:max-w-[1500px] h-[88vh]'
|
||||
? 'max-w-[95vw] sm:max-w-[90vw] lg:max-w-[85vw] 2xl:max-w-375 h-[88vh]'
|
||||
: id === 'connect_modal'
|
||||
? 'max-w-[95vw] sm:max-w-2xl md:max-w-3xl lg:max-w-4xl max-h-[88vh]'
|
||||
: id === 'changelog_modal'
|
||||
|
||||
@@ -170,12 +170,12 @@ export default function LightconeBar() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Results Grid */}
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 md:grid-cols-5 lg:grid-cols-6 xl:grid-cols-7 gap-3 auto-rows-fr max-h-[58vh] overflow-y-auto pr-1">
|
||||
{/* Results Grid - Compact multi-column layout */}
|
||||
<div className="grid grid-cols-3 sm:grid-cols-4 md:grid-cols-5 lg:grid-cols-6 xl:grid-cols-7 2xl:grid-cols-8 gap-2.5 sm:gap-3 pr-1">
|
||||
{listLightcone.map((item) => (
|
||||
<div
|
||||
key={item.ID}
|
||||
className="h-full flex flex-col"
|
||||
className="flex flex-col"
|
||||
onClick={() => {
|
||||
if (avatarSelected) {
|
||||
const avatar = avatars[avatarSelected.ID.toString()]
|
||||
|
||||
@@ -16,7 +16,7 @@ import useDetailDataStore from "@/stores/detailDataStore";
|
||||
import { getLocaleName } from '@/helper';
|
||||
import useLocaleStore from "@/stores/localeStore";
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { Trash2 } from "lucide-react";
|
||||
import { Trash2, SlidersHorizontal } from "lucide-react";
|
||||
import { toast } from "react-toastify";
|
||||
|
||||
export default function RelicsInfo() {
|
||||
@@ -249,6 +249,15 @@ export default function RelicsInfo() {
|
||||
return () => window.removeEventListener('keydown', handleEscKey);
|
||||
}, [isOpenRelic]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedRelicSlot) {
|
||||
setSelectedRelicSlot("1");
|
||||
}
|
||||
}, [selectedRelicSlot, setSelectedRelicSlot]);
|
||||
|
||||
const selectedRelic = selectedRelicSlot ? getRelic(selectedRelicSlot) : null;
|
||||
const isSelectedRelicEquipped = Boolean(selectedRelic);
|
||||
|
||||
return (
|
||||
<div className="w-full space-y-4">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4 sm:gap-6">
|
||||
@@ -274,7 +283,7 @@ export default function RelicsInfo() {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 6-Slot Grid completely free of box-in-box */}
|
||||
{/* 6-Slot Grid */}
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 gap-3 sm:gap-4">
|
||||
{["1", "2", "3", "4", "5", "6"].map((item, index) => (
|
||||
<div
|
||||
@@ -291,34 +300,30 @@ export default function RelicsInfo() {
|
||||
/>
|
||||
|
||||
{/* Micro Actions */}
|
||||
<div className="absolute top-0 right-0 opacity-0 group-hover:opacity-100 transition-opacity duration-200 flex gap-1">
|
||||
<div className="absolute top-1.5 right-1.5 opacity-0 group-hover:opacity-100 transition-all duration-200 flex gap-1 z-20">
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
handlerChangeRelic(item)
|
||||
e.stopPropagation();
|
||||
handlerChangeRelic(item);
|
||||
}}
|
||||
className="btn btn-xs btn-circle btn-primary shadow-xs"
|
||||
className="w-6 h-6 rounded-lg bg-primary text-primary-content shadow-xs hover:scale-110 active:scale-95 transition-all flex items-center justify-center border border-white/20 cursor-pointer"
|
||||
title={transI18n("changeRelic")}
|
||||
>
|
||||
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15.232 5.232l3.536 3.536M4 20h4l10.293-10.293a1 1 0 000-1.414l-2.586-2.586a1 1 0 00-1.414 0L4 16v4z" />
|
||||
</svg>
|
||||
<SlidersHorizontal className="w-3 h-3 stroke-[2.2]" />
|
||||
</button>
|
||||
|
||||
{getRelic(item) && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
requestDeleteRelic(item)
|
||||
e.stopPropagation();
|
||||
requestDeleteRelic(item);
|
||||
}}
|
||||
className="btn btn-xs btn-circle btn-error shadow-xs"
|
||||
className="w-6 h-6 rounded-lg bg-rose-500 text-white shadow-xs hover:scale-110 active:scale-95 transition-all flex items-center justify-center border border-white/20 cursor-pointer"
|
||||
title={transI18n("deleteRelic")}
|
||||
>
|
||||
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6M9 7V4h6v3m5 0H4" />
|
||||
</svg>
|
||||
<Trash2 className="w-3 h-3 stroke-[2.2]" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -332,22 +337,25 @@ export default function RelicsInfo() {
|
||||
type="button"
|
||||
disabled={!selectedRelicSlot}
|
||||
onClick={() => handlerChangeRelic(selectedRelicSlot)}
|
||||
className="btn btn-primary btn-sm rounded-xl font-bold shadow-xs"
|
||||
className="btn btn-primary btn-sm rounded-xl font-bold gap-2 shadow-xs transition-all active:scale-[0.98] cursor-pointer"
|
||||
>
|
||||
{transI18n("changeRelic")}
|
||||
<SlidersHorizontal className="w-4 h-4" />
|
||||
<span>{transI18n("changeRelic")}</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
disabled={!selectedRelicSlot || !getRelic(selectedRelicSlot)}
|
||||
disabled={!selectedRelicSlot || !isSelectedRelicEquipped}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
e.stopPropagation();
|
||||
if (selectedRelicSlot) {
|
||||
requestDeleteRelic(selectedRelicSlot)
|
||||
requestDeleteRelic(selectedRelicSlot);
|
||||
}
|
||||
}}
|
||||
className="btn btn-ghost btn-sm text-error hover:bg-error/20 rounded-xl disabled:opacity-40 disabled:pointer-events-none"
|
||||
className="btn btn-outline btn-error hover:btn-error btn-sm rounded-xl font-bold gap-2 transition-all active:scale-[0.98] cursor-pointer disabled:opacity-40 disabled:pointer-events-none"
|
||||
>
|
||||
{transI18n("deleteRelic")}
|
||||
<Trash2 className="w-4 h-4" />
|
||||
<span>{transI18n("deleteRelic")}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -513,7 +521,7 @@ export default function RelicsInfo() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleConfirmDelete}
|
||||
className="flex-1 h-10 px-4 rounded-xl font-bold text-xs sm:text-sm bg-gradient-to-r from-rose-500 to-red-600 hover:from-rose-400 hover:to-red-500 text-white shadow-md shadow-rose-500/25 hover:shadow-rose-500/40 transition-all flex items-center justify-center gap-1.5 cursor-pointer"
|
||||
className="flex-1 h-10 px-4 rounded-xl font-bold text-xs sm:text-sm bg-linear-to-r from-rose-500 to-red-600 hover:from-rose-400 hover:to-red-500 text-white shadow-md shadow-rose-500/25 hover:shadow-rose-500/40 transition-all flex items-center justify-center gap-1.5 cursor-pointer"
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
<span>{transI18n("confirmDelete")}</span>
|
||||
|
||||
@@ -702,9 +702,9 @@ export default function ShowCaseInfo() {
|
||||
<div className="bg-base-100 rounded-2xl p-4 sm:p-6 border border-base-300 shadow-sm space-y-6">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between pb-3 border-b border-base-content/10 flex-wrap gap-3">
|
||||
<h2 className="text-xl sm:text-2xl font-extrabold flex items-center gap-2.5">
|
||||
<div className="w-2 h-6 bg-linear-to-b from-primary to-accent rounded-full"></div>
|
||||
<span className="bg-clip-text text-transparent bg-linear-to-r from-primary to-accent">
|
||||
<h2 className="text-lg sm:text-xl font-bold flex items-center gap-2.5">
|
||||
<div className="w-1.5 h-5 bg-linear-to-b from-primary to-accent rounded-full"></div>
|
||||
<span className="bg-clip-text text-transparent bg-linear-to-r from-primary to-accent font-bold tracking-tight">
|
||||
{transI18n("showcaseCard")}
|
||||
</span>
|
||||
</h2>
|
||||
|
||||
@@ -152,7 +152,7 @@ export default function SkillsInfo() {
|
||||
{/* Visual Skill Tree Container */}
|
||||
<div className="flex justify-center items-center py-2">
|
||||
{traceButtons && avatarSelected && (
|
||||
<div className="relative w-full max-w-md aspect-square">
|
||||
<div className="relative w-full max-w-155 aspect-square select-none">
|
||||
<Image
|
||||
unoptimized
|
||||
crossOrigin="anonymous"
|
||||
@@ -164,7 +164,7 @@ export default function SkillsInfo() {
|
||||
style={{
|
||||
filter: (theme === "winter" || theme === "cupcake") ? "invert(1)" : "none"
|
||||
}}
|
||||
className="w-full h-full object-cover"
|
||||
className="w-full h-full object-contain pointer-events-none"
|
||||
/>
|
||||
{traceButtons.map((btn, index) => {
|
||||
if (!avatarSelected?.SkillTrees?.[btn.id]) {
|
||||
@@ -181,12 +181,12 @@ export default function SkillsInfo() {
|
||||
id={btn.id}
|
||||
className={`
|
||||
absolute rounded-full border border-black/70 cursor-pointer transition-all duration-150 ease-out flex justify-center items-center select-none overflow-visible
|
||||
${btn.size === "small" ? "w-[6.5vw] h-[6.5vw] md:w-[2.2vw] md:h-[2.2vw] bg-white" : ""}
|
||||
${btn.size === "medium" ? "w-[8.5vw] h-[8.5vw] md:w-[3vw] md:h-[3vw] bg-white" : ""}
|
||||
${btn.size === "big" ? "w-[9.5vw] h-[9.5vw] md:w-[3.5vw] md:h-[3.5vw] bg-black" : ""}
|
||||
${btn.size === "special" ? "w-[9.5vw] h-[9.5vw] md:w-[3.5vw] md:h-[3.5vw] bg-white" : ""}
|
||||
${btn.size === "memory" ? "w-[9.5vw] h-[9.5vw] md:w-[3.5vw] md:h-[3.5vw] bg-black" : ""}
|
||||
${btn.size === "elation" ? "w-[9.5vw] h-[9.5vw] md:w-[3.5vw] md:h-[3.5vw] bg-black" : ""}
|
||||
${btn.size === "small" ? "w-[7%] h-[7%] bg-white" : ""}
|
||||
${btn.size === "medium" ? "w-[9.6%] h-[9.6%] bg-white" : ""}
|
||||
${btn.size === "big" ? "w-[10%] h-[10%] bg-black" : ""}
|
||||
${btn.size === "special" ? "w-[10%] h-[10%] bg-white" : ""}
|
||||
${btn.size === "memory" ? "w-[10%] h-[10%] bg-black" : ""}
|
||||
${btn.size === "elation" ? "w-[10%] h-[10%] bg-black" : ""}
|
||||
${isSelected ? "ring-3 ring-cyan-400 ring-offset-2 ring-offset-base-100 shadow-[0_0_12px_rgba(34,211,238,0.8)] z-20" : "hover:ring-2 hover:ring-primary/40 z-10"}
|
||||
${!isLearned ? "opacity-40 grayscale" : "shadow-[0_0_6px_rgba(255,255,255,0.4)]"}
|
||||
`}
|
||||
@@ -257,7 +257,7 @@ export default function SkillsInfo() {
|
||||
)}
|
||||
|
||||
{isMajor && (
|
||||
<p className="z-12 text-[10px] sm:text-xs font-mono font-bold text-center rounded-full absolute translate-y-full mt-0.5 bg-black/80 text-white px-1.5 py-0.2 border border-white/20 left-1/2 -translate-x-1/2 shadow-xs whitespace-nowrap">
|
||||
<p className="z-12 text-[9px] sm:text-[10px] md:text-xs font-mono font-bold text-center rounded-full absolute -bottom-1.5 bg-black/85 text-white px-1.5 py-0.2 border border-white/20 left-1/2 -translate-x-1/2 shadow-xs whitespace-nowrap pointer-events-none">
|
||||
{`${avatarData?.data.skills?.[avatarSkillTree?.[btn.id]?.["1"]?.PointID] || 0}/${avatarSkillTree?.[btn.id]?.["1"]?.MaxLevel}`}
|
||||
</p>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user