Compare commits

..

8 Commits

Author SHA1 Message Date
ysm
2a40d05cac 2026年 03月 27日 星期五 13:26:11 CST 2026-03-27 13:26:11 +08:00
ysm
9cbae137a8 修复翻页时钟偏移并优化时间显示布局
- 修复翻页时钟动画开始时的位置偏移问题
- 将AM/PM显示移至时间数字左上角
- 添加日期(年月日星期)显示在时间数字右上角
- 统一数码管、翻页、滚动三种时钟的显示样式
- 调整AM/PM和日期字体为数字大小的20%
- 修复翻页和滚动时钟AM/PM显示错误(上午下午颠倒)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 13:24:27 +08:00
ysm
d2a9a94d3b 为翻页和滚动时钟独立添加12/24小时制切换
- FlipClock组件独立实现12/24H切换功能
- RollClock组件独立实现12/24H切换功能
- 分别使用独立的CSS样式(flip-format-toggle/roll-format-toggle)
- 各组件状态独立,互不干扰

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 00:17:32 +08:00
ysm
0f0809b1c0 添加12/24小时制切换功能
- 在数码管样式左侧添加 AM/PM 显示(12小时制)
- 添加12H/24H切换按钮,紧贴数字左侧显示
- 修复CSS无效代码

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 00:01:50 +08:00
ysm
8b43d2fb35 优化时钟响应式布局,防止小屏幕溢出
- 为小屏幕添加更保守的尺寸计算
- 调整数码管字体大小与高度匹配
- 添加溢出控制防止超出容器

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 22:51:32 +08:00
ysm
a431a5440f 调整时钟显示尺寸和布局
- 统一三种时钟样式的显示宽度为页面90%
- 增大翻页和滚动时钟的显示比例
- 优化响应式尺寸计算
- 调整页面布局使时钟占满宽度

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 22:12:22 +08:00
ysm
ec06bdc29f 优化时钟布局和响应式设计
- 将样式选择器移到页面顶部
- 添加响应式时钟尺寸,根据浏览器宽度自适应
- 修复样式选择器与时钟数字的重叠问题
- 优化 CSS 布局结构

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 21:44:57 +08:00
ysm
a3d4012064 修复翻页时钟动画效果
- 修正下半翻转片动画方向:从 -90° 向下翻转至 0°
- 移除透明度渐变,依靠 backface-visibility 实现硬切换效果
- 简化 transform 计算,使用 CSS transform-origin 控制旋转轴
- 调整层级确保上半翻转片在下半翻转片之上

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 17:08:02 +08:00
13 changed files with 619 additions and 145 deletions

View File

@ -5,6 +5,7 @@
## 项目概述
一个精简的 React + TypeScript + Vite 入门项目。
主要功能是在页面上显示数码风格的时钟。
- **Node 版本**: v24.14.0(在 `.nvmrc` 中指定)
- **包管理器**: npm
@ -66,3 +67,8 @@ src/
## 构建输出
生产构建输出到 `dist/` 目录ESLint 会忽略该目录)。
## 本地存储
本地存储使用localStorage用于保存用户设置。

4
dev.sh Executable file
View File

@ -0,0 +1,4 @@
#!/bin/bash
source ~/.nvm/nvm.sh
nvm use
npm run dev

11
docs/design/flip.md Normal file
View File

@ -0,0 +1,11 @@
# 翻页
这里是关于翻页的设计文档
翻页样式是模拟常见于机场的用于显示航班信息的老式机械翻页机。
基本样式就是上下两个方块,分别显示数字的上半部分和下半部分。
每次翻页时,上半部分会沿着上半部分的下边缘向下翻转,显示出后面背景的下一个数字的上半部分。然后,下半部分会沿着下半部分的上边缘向下翻转,显示出后面背景的下一个数字的下半部分。

4
release.sh Executable file
View File

@ -0,0 +1,4 @@
#!/bin/bash
source ~/.nvm/nvm.sh
nvm use

View File

@ -8,26 +8,11 @@
.app {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
align-items: stretch;
min-height: 100vh;
gap: 40px;
}
.clock-container {
display: flex;
justify-content: center;
align-items: center;
}
/* Digital clock style (default) */
.digital-clock {
font-family: 'DSEG7', monospace;
font-size: 15vw;
font-weight: bold;
color: #fff;
text-shadow: 0 0 20px rgba(255, 255, 255, 0.5);
letter-spacing: 0.1em;
width: 100%;
padding-top: 40px;
box-sizing: border-box;
}
/* Style selector */
@ -38,6 +23,113 @@
padding: 8px;
background-color: rgba(255, 255, 255, 0.05);
border-radius: 12px;
z-index: 10;
flex-shrink: 0;
margin-bottom: 60px;
align-self: center;
}
.clock-wrapper {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
gap: 0;
width: 100%;
padding: 0 20px;
box-sizing: border-box;
}
.digital-clock-outer {
display: flex;
flex-direction: row;
align-items: center;
gap: 0;
}
.digital-clock-side-panel {
display: flex;
flex-direction: column;
align-items: flex-end;
justify-content: center;
margin-right: 8px;
}
.digital-clock-wrapper {
position: relative;
display: flex;
flex-direction: column;
align-items: stretch;
}
.digital-clock-header {
display: flex;
flex-direction: row;
align-items: baseline;
justify-content: space-between;
margin-bottom: 4px;
}
.digital-clock-header-left {
display: flex;
flex-direction: row;
align-items: baseline;
}
.digital-clock-period {
font-family: Arial, sans-serif;
font-weight: 600;
color: #fff;
text-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
line-height: 1;
}
.digital-clock-date {
font-family: Arial, sans-serif;
font-weight: 600;
color: #fff;
text-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
line-height: 1;
white-space: nowrap;
}
.format-toggle-side {
padding: 4px 8px;
font-size: 12px;
font-weight: 500;
color: #888;
background-color: rgba(255, 255, 255, 0.05);
border: none;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s ease;
white-space: nowrap;
}
.format-toggle-side:hover {
color: #fff;
background-color: rgba(255, 255, 255, 0.15);
}
.clock-container {
display: flex;
justify-content: center;
align-items: center;
flex: 1;
max-width: 100vw;
overflow: hidden;
box-sizing: border-box;
}
.digital-clock {
font-family: 'DSEG7', monospace;
font-weight: bold;
color: #fff;
text-shadow: 0 0 20px rgba(255, 255, 255, 0.5);
letter-spacing: 0.05em;
white-space: nowrap;
max-width: 100%;
overflow: hidden;
}
.style-button {
@ -64,10 +156,6 @@
/* Responsive adjustments */
@media (max-width: 768px) {
.digital-clock {
font-size: 12vw;
}
.style-selector {
gap: 8px;
}

View File

@ -5,17 +5,74 @@ import './App.css'
type ClockStyle = 'digital' | 'flip' | 'roll'
function DigitalClock({ time }: { time: string }) {
interface DateInfo {
dateString: string
weekday: string
}
function formatDate(date: Date): DateInfo {
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const weekdays = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']
const weekday = weekdays[date.getDay()]
return {
dateString: `${year}-${month}-${day}`,
weekday
}
}
function DigitalClock({ time, size, period, is24Hour, onToggleFormat, dateInfo }: { time: string; size: number; period?: string; is24Hour: boolean; onToggleFormat: () => void; dateInfo: DateInfo }) {
const fontSize = Math.floor(size * 0.95)
const periodSize = Math.floor(size * 0.20)
const dateSize = Math.floor(size * 0.20)
return (
<div className="digital-clock">
<div className="digital-clock-outer">
<div className="digital-clock-side-panel">
<button
className="format-toggle-side"
onClick={onToggleFormat}
>
{is24Hour ? '24H' : '12H'}
</button>
</div>
<div className="digital-clock-wrapper">
<div className="digital-clock-header">
<div className="digital-clock-header-left">
{!is24Hour && period && (
<span
className="digital-clock-period"
style={{ fontSize: `${periodSize}px` }}
>
{period}
</span>
)}
</div>
<span
className="digital-clock-date"
style={{ fontSize: `${dateSize}px` }}
>
{dateInfo.dateString} {dateInfo.weekday}
</span>
</div>
<div
className="digital-clock"
style={{ fontSize: `${fontSize}px`, lineHeight: '1' }}
>
{time}
</div>
</div>
</div>
)
}
function App() {
const [time, setTime] = useState(new Date())
const [clockStyle, setClockStyle] = useState<ClockStyle>('digital')
const [clockSize, setClockSize] = useState(120)
const [is24Hour, setIs24Hour] = useState(true)
useEffect(() => {
const timer = setInterval(() => {
@ -24,26 +81,67 @@ function App() {
return () => clearInterval(timer)
}, [])
const timeString = `${String(time.getHours()).padStart(2, '0')}:${String(time.getMinutes()).padStart(2, '0')}:${String(time.getSeconds()).padStart(2, '0')}`
// Responsive clock size - fit within screen width
useEffect(() => {
const updateSize = () => {
const width = window.innerWidth
// More conservative for small screens
const margin = width < 480 ? 40 : 60
const availableWidth = width - margin
// Use larger divisor for small screens to prevent overflow
const divisor = width < 480 ? 7 : 6
const newSize = Math.floor(availableWidth / divisor)
setClockSize(Math.min(newSize, 200))
}
updateSize()
window.addEventListener('resize', updateSize)
return () => window.removeEventListener('resize', updateSize)
}, [])
// Format time based on 12/24 hour setting
const formatTime = () => {
let hours = time.getHours()
const minutes = String(time.getMinutes()).padStart(2, '0')
const seconds = String(time.getSeconds()).padStart(2, '0')
let period: string | undefined
if (!is24Hour) {
period = hours >= 12 ? 'PM' : 'AM'
hours = hours % 12 || 12
}
return {
timeString: `${String(hours).padStart(2, '0')}:${minutes}:${seconds}`,
period
}
}
const { timeString, period } = formatTime()
const dateInfo = formatDate(time)
const renderClock = () => {
switch (clockStyle) {
case 'flip':
return <FlipClock time={timeString} size={120} />
return <FlipClock time={timeString} size={clockSize} dateInfo={dateInfo} period={period} />
case 'roll':
return <RollClock time={timeString} size={120} />
return <RollClock time={timeString} size={clockSize} dateInfo={dateInfo} period={period} />
case 'digital':
default:
return <DigitalClock time={timeString} />
return (
<DigitalClock
time={timeString}
size={clockSize}
period={period}
is24Hour={is24Hour}
onToggleFormat={() => setIs24Hour(!is24Hour)}
dateInfo={dateInfo}
/>
)
}
}
return (
<div className="app">
<div className="clock-container">
{renderClock()}
</div>
<div className="style-selector">
<button
className={`style-button ${clockStyle === 'digital' ? 'active' : ''}`}
@ -64,6 +162,12 @@ function App() {
</button>
</div>
<div className="clock-wrapper">
<div className="clock-container">
{renderClock()}
</div>
</div>
</div>
)
}

View File

@ -1,3 +1,72 @@
.flip-clock-outer {
display: flex;
flex-direction: row;
align-items: center;
gap: 0;
}
.flip-clock-side-panel {
display: flex;
flex-direction: column;
align-items: flex-end;
justify-content: center;
margin-right: 8px;
}
.flip-clock-period {
font-family: Arial, sans-serif;
font-weight: 600;
color: #fff;
text-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
line-height: 1;
}
.flip-format-toggle {
padding: 4px 8px;
font-size: 12px;
font-weight: 500;
color: #888;
background-color: rgba(255, 255, 255, 0.05);
border: none;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s ease;
white-space: nowrap;
}
.flip-format-toggle:hover {
color: #fff;
background-color: rgba(255, 255, 255, 0.15);
}
.flip-clock-main {
display: flex;
flex-direction: column;
align-items: stretch;
}
.flip-clock-header {
display: flex;
flex-direction: row;
align-items: baseline;
justify-content: space-between;
margin-bottom: 4px;
}
.flip-clock-header-left {
display: flex;
flex-direction: row;
align-items: baseline;
}
.flip-clock-period {
font-family: Arial, sans-serif;
font-weight: 600;
color: #fff;
text-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
line-height: 1;
}
.flip-clock-container {
display: flex;
flex-direction: row;
@ -5,6 +74,15 @@
justify-content: center;
}
.flip-clock-date {
font-family: Arial, sans-serif;
font-weight: 600;
color: #fff;
text-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
line-height: 1;
white-space: nowrap;
}
.flip-clock-digit-group {
display: flex;
flex-direction: row;

View File

@ -1,20 +1,63 @@
import { useState } from 'react';
import { FlipDigit } from './FlipDigit';
import './FlipClock.css';
interface DateInfo {
dateString: string;
weekday: string;
}
interface FlipClockProps {
time: string; // "HH:MM:SS" format
size: number;
dateInfo: DateInfo;
period?: string;
}
export function FlipClock({ time, size }: FlipClockProps) {
const [hours, minutes, seconds] = time.split(':');
export function FlipClock({ time, size, dateInfo, period }: FlipClockProps) {
const [is24Hour, setIs24Hour] = useState(true);
// Parse time - it's already in 12h format when period is provided
const [hoursStr, minutes, seconds] = time.split(':');
const displayHours = hoursStr;
const periodSize = size * 0.20;
const dateSize = size * 0.20;
return (
<div className="flip-clock-outer">
<div className="flip-clock-side-panel">
<button
className="flip-format-toggle"
onClick={() => setIs24Hour(!is24Hour)}
>
{is24Hour ? '24H' : '12H'}
</button>
</div>
<div className="flip-clock-main">
<div className="flip-clock-header">
<div className="flip-clock-header-left">
{period && (
<span
className="flip-clock-period"
style={{ fontSize: `${periodSize}px` }}
>
{period}
</span>
)}
</div>
<span
className="flip-clock-date"
style={{ fontSize: `${dateSize}px` }}
>
{dateInfo.dateString} {dateInfo.weekday}
</span>
</div>
<div className="flip-clock-container">
{/* Hours */}
<div className="flip-clock-digit-group">
<FlipDigit value={parseInt(hours[0], 10)} size={size} />
<FlipDigit value={parseInt(hours[1], 10)} size={size} />
<FlipDigit value={parseInt(displayHours[0], 10)} size={size} />
<FlipDigit value={parseInt(displayHours[1], 10)} size={size} />
</div>
<span
@ -49,5 +92,7 @@ export function FlipClock({ time, size }: FlipClockProps) {
<FlipDigit value={parseInt(seconds[1], 10)} size={size} />
</div>
</div>
</div>
</div>
);
}

View File

@ -3,6 +3,7 @@
border-radius: 4px;
background-color: #1a1a1a;
overflow: hidden;
perspective: 1000px;
}
.flip-digit-bg-top {
@ -69,7 +70,8 @@
border-top-left-radius: 4px;
border-top-right-radius: 4px;
backface-visibility: hidden;
z-index: 10;
z-index: 11;
/* 旋转轴在底部边缘(卡片中线位置) */
transform-origin: center bottom;
}
@ -87,6 +89,7 @@
border-bottom-right-radius: 4px;
backface-visibility: hidden;
z-index: 10;
/* 旋转轴在顶部边缘(卡片中线位置) */
transform-origin: center top;
}

View File

@ -33,23 +33,28 @@ export function FlipDigit({ value, size }: FlipDigitProps) {
// Finish animation
const finishAnimation = () => {
setIsAnimating(false);
requestAnimationFrame(() => {
setTopRotation(0);
setBottomRotation(-90);
setFlipBottomValue(null);
setIsAnimating(false);
animating.current = false;
});
};
// Start bottom animation after top completes
const startBottomAnimation = (targetValue: number) => {
// 上半片翻转完成后,更新上半翻转片的值为新值
// 这样当 finishAnimation 重置 rotation 时,上半翻转片显示正确的值
setFlipTopValue(targetValue);
// Animate bottom flap from -90deg to 0deg
// 第二阶段:下半片从-90°向下翻转90°到0°
requestAnimationFrame(() => {
setBottomRotation(0);
});
// After bottom animation completes, update bgBottom and finish
// 下半片翻转完成后,背景下半更新为新值
setTimeout(() => {
setBgBottomValue(targetValue);
setTimeout(() => {
@ -62,7 +67,6 @@ export function FlipDigit({ value, size }: FlipDigitProps) {
useEffect(() => {
if (value !== bgTopValue && !animating.current) {
animating.current = true;
setIsAnimating(true);
// Save current value as flip top value
setFlipTopValue(bgTopValue);
@ -71,14 +75,24 @@ export function FlipDigit({ value, size }: FlipDigitProps) {
// Update bgTop immediately (revealed when top flips away)
setBgTopValue(value);
// Reset rotation states
// First, ensure transition is disabled before resetting rotation
setIsAnimating(false);
// Reset rotation states instantly (without transition)
setTopRotation(0);
setBottomRotation(-90);
// Start top animation after a brief delay to ensure state is set
// Wait for browser to apply the reset (without transition)
requestAnimationFrame(() => {
// Now enable transition for the actual animation
setIsAnimating(true);
// Wait one more frame to ensure transition is enabled
requestAnimationFrame(() => {
// Start top animation: flip from 0° to 90°
setTopRotation(90);
});
});
// After top animation completes, start bottom animation
setTimeout(() => {
@ -109,14 +123,12 @@ export function FlipDigit({ value, size }: FlipDigitProps) {
const topFlipStyle = {
height: `${halfHeight}px`,
transform: `perspective(1000px) translateY(${halfHeight * 0.5}px) rotateX(${topRotation}deg) translateY(${-halfHeight * 0.5}px)`,
opacity: topRotation > 60 ? 0 : 1,
transform: `rotateX(${topRotation}deg)`,
};
const bottomFlipStyle = {
height: `${halfHeight}px`,
transform: `perspective(1000px) translateY(${-halfHeight * 0.5}px) rotateX(${bottomRotation}deg) translateY(${halfHeight * 0.5}px)`,
opacity: bottomRotation > -60 ? 1 : 0,
transform: `rotateX(${bottomRotation}deg)`,
};
const centerLineStyle = {
@ -145,7 +157,7 @@ export function FlipDigit({ value, size }: FlipDigitProps) {
style={{
...halfHeightStyle,
...topFlipStyle,
transition: isAnimating ? 'transform 300ms ease-in, opacity 300ms ease-in' : 'none',
transition: isAnimating ? 'transform 300ms ease-in' : 'none',
}}
>
<span className="flip-digit-text flip-digit-top-text" style={topTextStyle}>
@ -160,7 +172,7 @@ export function FlipDigit({ value, size }: FlipDigitProps) {
style={{
...halfHeightStyle,
...bottomFlipStyle,
transition: isAnimating ? 'transform 300ms ease-out, opacity 300ms ease-out' : 'none',
transition: isAnimating ? 'transform 300ms ease-out' : 'none',
}}
>
<span className="flip-digit-text flip-digit-bottom-text" style={bottomTextStyle}>

View File

@ -1,9 +1,78 @@
.roll-clock-outer {
display: flex;
flex-direction: row;
align-items: center;
gap: 0;
}
.roll-clock-side-panel {
display: flex;
flex-direction: column;
align-items: flex-end;
justify-content: center;
margin-right: 8px;
}
.roll-clock-period {
font-family: Arial, sans-serif;
font-weight: 600;
color: #fff;
text-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
line-height: 1;
}
.roll-format-toggle {
padding: 4px 8px;
font-size: 12px;
font-weight: 500;
color: #888;
background-color: rgba(255, 255, 255, 0.05);
border: none;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s ease;
white-space: nowrap;
}
.roll-format-toggle:hover {
color: #fff;
background-color: rgba(255, 255, 255, 0.15);
}
.roll-clock-wrapper {
display: flex;
flex-direction: row;
align-items: center;
}
.roll-clock-main {
display: flex;
flex-direction: column;
align-items: stretch;
}
.roll-clock-header {
display: flex;
flex-direction: row;
align-items: baseline;
justify-content: space-between;
margin-bottom: 4px;
}
.roll-clock-header-left {
display: flex;
flex-direction: row;
align-items: baseline;
}
.roll-clock-period {
font-family: Arial, sans-serif;
font-weight: 600;
color: #fff;
text-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
line-height: 1;
}
.roll-clock-container {
display: flex;
flex-direction: row;
@ -11,6 +80,15 @@
justify-content: center;
}
.roll-clock-date {
font-family: Arial, sans-serif;
font-weight: 600;
color: #fff;
text-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
line-height: 1;
white-space: nowrap;
}
.roll-clock-digit-group {
display: flex;
flex-direction: row;

View File

@ -2,17 +2,30 @@ import { useState } from 'react';
import { RollDigit } from './RollDigit';
import './RollClock.css';
interface DateInfo {
dateString: string;
weekday: string;
}
interface RollClockProps {
time: string; // "HH:MM:SS" format
size: number;
dateInfo: DateInfo;
period?: string;
}
export function RollClock({ time, size }: RollClockProps) {
export function RollClock({ time, size, dateInfo, period }: RollClockProps) {
// Roll direction: 'down' = roll down, 'up' = roll up
const [direction, setDirection] = useState<'down' | 'up'>('down');
const [is24Hour, setIs24Hour] = useState(true);
// Parse time - it's already in 12h format when period is provided
const [hoursStr, minutes, seconds] = time.split(':');
const displayHours = hoursStr;
const [hours, minutes, seconds] = time.split(':');
const buttonSize = size * 0.25;
const periodSize = size * 0.20;
const dateSize = size * 0.20;
// Toggle direction
const toggleDirection = () => {
@ -20,12 +33,40 @@ export function RollClock({ time, size }: RollClockProps) {
};
return (
<div className="roll-clock-outer">
<div className="roll-clock-side-panel">
<button
className="roll-format-toggle"
onClick={() => setIs24Hour(!is24Hour)}
>
{is24Hour ? '24H' : '12H'}
</button>
</div>
<div className="roll-clock-wrapper">
<div className="roll-clock-main">
<div className="roll-clock-header">
<div className="roll-clock-header-left">
{!is24Hour && period && (
<span
className="roll-clock-period"
style={{ fontSize: `${periodSize}px` }}
>
{period}
</span>
)}
</div>
<span
className="roll-clock-date"
style={{ fontSize: `${dateSize}px` }}
>
{dateInfo.dateString} {dateInfo.weekday}
</span>
</div>
<div className="roll-clock-container">
{/* Hours */}
<div className="roll-clock-digit-group">
<RollDigit value={parseInt(hours[0], 10)} size={size} direction={direction} />
<RollDigit value={parseInt(hours[1], 10)} size={size} direction={direction} />
<RollDigit value={parseInt(displayHours[0], 10)} size={size} direction={direction} />
<RollDigit value={parseInt(displayHours[1], 10)} size={size} direction={direction} />
</div>
<span
@ -60,6 +101,7 @@ export function RollClock({ time, size }: RollClockProps) {
<RollDigit value={parseInt(seconds[1], 10)} size={size} direction={direction} />
</div>
</div>
</div>
{/* Direction toggle button */}
<button
@ -80,5 +122,6 @@ export function RollClock({ time, size }: RollClockProps) {
</span>
</button>
</div>
</div>
);
}

View File

@ -51,11 +51,9 @@
}
#root {
width: 1126px;
max-width: 100%;
width: 100%;
margin: 0 auto;
text-align: center;
border-inline: 1px solid var(--border);
min-height: 100svh;
display: flex;
flex-direction: column;