style(study): visual refresh matching phase 7D theme — 7D.12
This commit is contained in:
@@ -2,7 +2,8 @@
|
||||
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import styled from 'styled-components';
|
||||
import styled, { css } from 'styled-components';
|
||||
import { Icon } from '@/components/ui/Icon';
|
||||
import { theme } from '@/styles/theme';
|
||||
import {
|
||||
api,
|
||||
@@ -20,6 +21,7 @@ import {
|
||||
HelpText,
|
||||
Input,
|
||||
Label,
|
||||
PageHeader,
|
||||
SectionTitle,
|
||||
Select,
|
||||
Stack,
|
||||
@@ -165,10 +167,12 @@ function StudyBody() {
|
||||
if (subjects.length === 0) {
|
||||
return (
|
||||
<Empty>
|
||||
<EmptyEmoji>📚</EmptyEmoji>
|
||||
<EmptyIconWrap>
|
||||
<Icon name="book-open" weight="duotone" size={36} />
|
||||
</EmptyIconWrap>
|
||||
<EmptyTitle>먼저 과목을 만들어줘</EmptyTitle>
|
||||
<EmptySub>학습 기록을 남기려면 과목이 하나 이상 필요해.</EmptySub>
|
||||
<Button $size="md" onClick={() => router.push('/subjects')}>
|
||||
<Button $size="md" $variant="primary" onClick={() => router.push('/subjects')}>
|
||||
과목 만들러 가기
|
||||
</Button>
|
||||
</Empty>
|
||||
@@ -177,32 +181,45 @@ function StudyBody() {
|
||||
|
||||
return (
|
||||
<Wrap>
|
||||
<Header>
|
||||
<Title>새 학습 기록</Title>
|
||||
<Sub>문제를 풀고 바로 기록하면 다음 복습이 자동으로 잡혀.</Sub>
|
||||
</Header>
|
||||
<HeaderCard>
|
||||
<PageHeader
|
||||
eyebrow="Study Log"
|
||||
title="새 학습 기록"
|
||||
subtitle="문제를 풀고 바로 기록하면 다음 복습이 자동으로 잡혀."
|
||||
right={
|
||||
<HeaderBadge>
|
||||
<Icon name="clock-counter-clockwise" size={16} />
|
||||
즉시 복습 큐 반영
|
||||
</HeaderBadge>
|
||||
}
|
||||
/>
|
||||
</HeaderCard>
|
||||
|
||||
<ModeTabs>
|
||||
<ModeTabs role="tablist" aria-label="학습 기록 방식">
|
||||
<TabBtn
|
||||
type="button"
|
||||
$active={mode === 'manual'}
|
||||
aria-pressed={mode === 'manual'}
|
||||
aria-selected={mode === 'manual'}
|
||||
onClick={() => setMode('manual')}
|
||||
>
|
||||
✍️ 직접 입력
|
||||
<Icon name="pencil-simple" size={16} weight={mode === 'manual' ? 'fill' : 'regular'} />
|
||||
직접 입력
|
||||
</TabBtn>
|
||||
<TabBtn
|
||||
type="button"
|
||||
$active={mode === 'problemset'}
|
||||
aria-pressed={mode === 'problemset'}
|
||||
aria-selected={mode === 'problemset'}
|
||||
onClick={() => setMode('problemset')}
|
||||
>
|
||||
📚 문제집에서 가져오기
|
||||
<Icon name="book-open" size={16} weight={mode === 'problemset' ? 'fill' : 'regular'} />
|
||||
문제집에서 가져오기
|
||||
</TabBtn>
|
||||
</ModeTabs>
|
||||
|
||||
<form onSubmit={submit}>
|
||||
<Card>
|
||||
<StudyCard>
|
||||
<Stack $gap={theme.space.md}>
|
||||
{mode === 'manual' ? (
|
||||
<ManualHeader
|
||||
@@ -237,7 +254,9 @@ function StudyBody() {
|
||||
$tone="success"
|
||||
onClick={() => setResult('correct')}
|
||||
>
|
||||
<ResultMark>✓</ResultMark>
|
||||
<ResultMark>
|
||||
<Icon name="check" weight="bold" size={18} />
|
||||
</ResultMark>
|
||||
<ResultLabel>맞음</ResultLabel>
|
||||
</ResultChoice>
|
||||
<ResultChoice
|
||||
@@ -246,7 +265,9 @@ function StudyBody() {
|
||||
$tone="warning"
|
||||
onClick={() => setResult('partial')}
|
||||
>
|
||||
<ResultMark>△</ResultMark>
|
||||
<ResultMark>
|
||||
<Icon name="triangle" weight="fill" size={18} />
|
||||
</ResultMark>
|
||||
<ResultLabel>부분</ResultLabel>
|
||||
</ResultChoice>
|
||||
<ResultChoice
|
||||
@@ -255,21 +276,25 @@ function StudyBody() {
|
||||
$tone="danger"
|
||||
onClick={() => setResult('incorrect')}
|
||||
>
|
||||
<ResultMark>✗</ResultMark>
|
||||
<ResultMark>
|
||||
<Icon name="x" weight="bold" size={18} />
|
||||
</ResultMark>
|
||||
<ResultLabel>틀림</ResultLabel>
|
||||
</ResultChoice>
|
||||
</ResultGrid>
|
||||
|
||||
<div>
|
||||
<Label>체감 난이도 · {Math.round(difficulty * 100)}%</Label>
|
||||
<input
|
||||
<SliderHeader>
|
||||
<Label>체감 난이도</Label>
|
||||
<SliderValue>{Math.round(difficulty * 100)}%</SliderValue>
|
||||
</SliderHeader>
|
||||
<RangeInput
|
||||
type="range"
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.05}
|
||||
value={difficulty}
|
||||
onChange={(e) => setDifficulty(Number(e.target.value))}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
<HelpText>0%에 가까울수록 쉬움, 100%에 가까울수록 어려움.</HelpText>
|
||||
</div>
|
||||
@@ -277,39 +302,50 @@ function StudyBody() {
|
||||
<Row>
|
||||
<Field>
|
||||
<Label>기준 정답률 (선택)</Label>
|
||||
<Input
|
||||
type="number"
|
||||
min={0}
|
||||
max={100}
|
||||
value={baseCorrectRate}
|
||||
onChange={(e) => setBaseCorrectRate(e.target.value)}
|
||||
placeholder="예) 45"
|
||||
/>
|
||||
<FieldFrame>
|
||||
<FieldInput
|
||||
type="number"
|
||||
min={0}
|
||||
max={100}
|
||||
value={baseCorrectRate}
|
||||
onChange={(e) => setBaseCorrectRate(e.target.value)}
|
||||
placeholder="예) 45"
|
||||
/>
|
||||
</FieldFrame>
|
||||
<HelpText>수능 기출이면 공식 정답률(%)을 넣어줘.</HelpText>
|
||||
</Field>
|
||||
|
||||
<Field>
|
||||
<Label>풀이 시간 (초, 선택)</Label>
|
||||
<Input
|
||||
type="number"
|
||||
min={0}
|
||||
value={timeSpent}
|
||||
onChange={(e) => setTimeSpent(e.target.value)}
|
||||
placeholder="예) 180"
|
||||
/>
|
||||
<FieldFrame>
|
||||
<FieldInput
|
||||
type="number"
|
||||
min={0}
|
||||
value={timeSpent}
|
||||
onChange={(e) => setTimeSpent(e.target.value)}
|
||||
placeholder="예) 180"
|
||||
/>
|
||||
</FieldFrame>
|
||||
</Field>
|
||||
</Row>
|
||||
|
||||
<div>
|
||||
<Label>메모 (선택)</Label>
|
||||
<Textarea
|
||||
value={memo}
|
||||
onChange={(e) => setMemo(e.target.value)}
|
||||
placeholder="어디서 막혔는지, 무엇을 배웠는지 짧게 남겨봐."
|
||||
/>
|
||||
<FieldFrame>
|
||||
<FieldTextarea
|
||||
value={memo}
|
||||
onChange={(e) => setMemo(e.target.value)}
|
||||
placeholder="어디서 막혔는지, 무엇을 배웠는지 짧게 남겨봐."
|
||||
/>
|
||||
</FieldFrame>
|
||||
</div>
|
||||
|
||||
{err && <ErrorText>{err}</ErrorText>}
|
||||
{err && (
|
||||
<ErrorBox role="alert" aria-live="polite">
|
||||
<Icon name="info" size={16} weight="bold" />
|
||||
<ErrorText as="span">{err}</ErrorText>
|
||||
</ErrorBox>
|
||||
)}
|
||||
|
||||
<ButtonRow>
|
||||
<Button
|
||||
@@ -321,12 +357,12 @@ function StudyBody() {
|
||||
>
|
||||
취소
|
||||
</Button>
|
||||
<Button type="submit" $size="md" disabled={submitting}>
|
||||
<PrimarySubmit type="submit" $size="lg" $block disabled={submitting}>
|
||||
{submitting ? '저장 중...' : '기록하고 복습 예약'}
|
||||
</Button>
|
||||
</PrimarySubmit>
|
||||
</ButtonRow>
|
||||
</Stack>
|
||||
</Card>
|
||||
</StudyCard>
|
||||
</form>
|
||||
</Wrap>
|
||||
);
|
||||
@@ -357,42 +393,48 @@ function ManualHeader({
|
||||
<Row>
|
||||
<Field>
|
||||
<Label>과목</Label>
|
||||
<Select
|
||||
value={subjectId}
|
||||
onChange={(e) => setSubjectId(Number(e.target.value))}
|
||||
>
|
||||
{subjects.map((s) => (
|
||||
<option key={s.id} value={s.id}>
|
||||
{s.name}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
<FieldFrame>
|
||||
<FieldSelect
|
||||
value={subjectId}
|
||||
onChange={(e) => setSubjectId(Number(e.target.value))}
|
||||
>
|
||||
{subjects.map((s) => (
|
||||
<option key={s.id} value={s.id}>
|
||||
{s.name}
|
||||
</option>
|
||||
))}
|
||||
</FieldSelect>
|
||||
</FieldFrame>
|
||||
</Field>
|
||||
<Field>
|
||||
<Label>태그 (선택)</Label>
|
||||
<Select
|
||||
value={tagId}
|
||||
onChange={(e) =>
|
||||
setTagId(e.target.value === '' ? '' : Number(e.target.value))
|
||||
}
|
||||
>
|
||||
<option value="">— 없음 —</option>
|
||||
{currentTags?.map((t) => (
|
||||
<option key={t.id} value={t.id}>
|
||||
{t.name}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
<FieldFrame>
|
||||
<FieldSelect
|
||||
value={tagId}
|
||||
onChange={(e) =>
|
||||
setTagId(e.target.value === '' ? '' : Number(e.target.value))
|
||||
}
|
||||
>
|
||||
<option value="">— 없음 —</option>
|
||||
{currentTags?.map((t) => (
|
||||
<option key={t.id} value={t.id}>
|
||||
{t.name}
|
||||
</option>
|
||||
))}
|
||||
</FieldSelect>
|
||||
</FieldFrame>
|
||||
</Field>
|
||||
</Row>
|
||||
<div>
|
||||
<Label>문제 제목</Label>
|
||||
<Input
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
placeholder="예) 2024 수능 수학 15번"
|
||||
required
|
||||
/>
|
||||
<FieldFrame>
|
||||
<FieldInput
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
placeholder="예) 2024 수능 수학 15번"
|
||||
required
|
||||
/>
|
||||
</FieldFrame>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
@@ -434,18 +476,20 @@ function ProblemSetHeader({
|
||||
<>
|
||||
<div>
|
||||
<Label>문제집</Label>
|
||||
<Select
|
||||
value={selectedSetId}
|
||||
onChange={(e) =>
|
||||
setSelectedSetId(e.target.value === '' ? '' : Number(e.target.value))
|
||||
}
|
||||
>
|
||||
{problemSets.map((ps) => (
|
||||
<option key={ps.id} value={ps.id}>
|
||||
[{ps.year}·{ps.subjectName}] {ps.title} ({ps._count.problems}문제)
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
<FieldFrame>
|
||||
<FieldSelect
|
||||
value={selectedSetId}
|
||||
onChange={(e) =>
|
||||
setSelectedSetId(e.target.value === '' ? '' : Number(e.target.value))
|
||||
}
|
||||
>
|
||||
{problemSets.map((ps) => (
|
||||
<option key={ps.id} value={ps.id}>
|
||||
[{ps.year}·{ps.subjectName}] {ps.title} ({ps._count.problems}문제)
|
||||
</option>
|
||||
))}
|
||||
</FieldSelect>
|
||||
</FieldFrame>
|
||||
</div>
|
||||
{setDetail && (
|
||||
<>
|
||||
@@ -476,15 +520,21 @@ function ProblemSetHeader({
|
||||
})}
|
||||
</ProblemList>
|
||||
{matchedSubject ? (
|
||||
<HelpText>
|
||||
✓ 너의 과목 <b>{matchedSubject.name}</b> 에 자동 매핑됐어.
|
||||
</HelpText>
|
||||
<MappingHint>
|
||||
<Icon name="check-circle" size={15} weight="fill" />
|
||||
<span>
|
||||
너의 과목 <b>{matchedSubject.name}</b> 에 자동 매핑됐어.
|
||||
</span>
|
||||
</MappingHint>
|
||||
) : (
|
||||
<HelpText>
|
||||
⚠ 이 문제집의 과목(<b>{setDetail.subjectName}</b>) 이름과
|
||||
일치하는 내 과목이 없어. 직접 입력 탭으로 전환해서 과목을
|
||||
먼저 만들거나, 아래에서 수동으로 선택해.
|
||||
</HelpText>
|
||||
<MappingHint $tone="warning">
|
||||
<Icon name="info" size={15} weight="bold" />
|
||||
<span>
|
||||
이 문제집의 과목(<b>{setDetail.subjectName}</b>) 이름과 일치하는 내
|
||||
과목이 없어. 직접 입력 탭으로 전환해서 과목을 먼저 만들거나,
|
||||
아래에서 수동으로 선택해.
|
||||
</span>
|
||||
</MappingHint>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
@@ -503,38 +553,64 @@ const Wrap = styled.div`
|
||||
max-width: 720px;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
color: ${theme.color.textBright};
|
||||
`;
|
||||
|
||||
const Header = styled.header``;
|
||||
const Title = styled.h1`
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
const HeaderCard = styled.div`
|
||||
position: relative;
|
||||
padding: ${theme.space.lg};
|
||||
border-radius: 24px;
|
||||
border: 1px solid ${theme.color.borderSoftAlpha};
|
||||
background:
|
||||
radial-gradient(circle at top right, rgba(79, 70, 229, 0.16), transparent 34%),
|
||||
linear-gradient(180deg, rgba(21, 21, 28, 0.96) 0%, rgba(17, 17, 24, 0.98) 100%);
|
||||
box-shadow: ${theme.shadow.cardElevated};
|
||||
overflow: hidden;
|
||||
`;
|
||||
const Sub = styled.p`
|
||||
font-size: 13px;
|
||||
|
||||
const HeaderBadge = styled.div`
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-height: 38px;
|
||||
padding: 0 14px;
|
||||
border-radius: ${theme.radius.pill};
|
||||
border: 1px solid ${theme.color.borderSoftAlpha};
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
color: ${theme.color.textSub};
|
||||
margin-top: 4px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
`;
|
||||
|
||||
const ModeTabs = styled.div`
|
||||
display: inline-flex;
|
||||
background: ${theme.color.surface2};
|
||||
border: 1px solid ${theme.color.border};
|
||||
border-radius: ${theme.radius.pill};
|
||||
padding: 4px;
|
||||
gap: 4px;
|
||||
align-self: flex-start;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
`;
|
||||
|
||||
const TabBtn = styled.button<{ $active: boolean }>`
|
||||
padding: 8px 16px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-height: 42px;
|
||||
padding: 0 16px;
|
||||
border-radius: ${theme.radius.pill};
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: ${({ $active }) => ($active ? 'white' : theme.color.textSub)};
|
||||
background: ${({ $active }) => ($active ? theme.color.accent : 'transparent')};
|
||||
transition:
|
||||
background 0.15s ease,
|
||||
color 0.15s ease;
|
||||
border: 1px solid
|
||||
${({ $active }) =>
|
||||
$active ? 'rgba(129, 140, 248, 0.42)' : theme.color.borderSoftAlpha};
|
||||
background: ${({ $active }) =>
|
||||
$active ? 'rgba(79, 70, 229, 0.2)' : 'rgba(255, 255, 255, 0.02)'};
|
||||
color: ${({ $active }) => ($active ? theme.color.textBright : theme.color.textSub)};
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
transition: border-color 0.18s ease, background 0.18s ease, color 0.18s ease;
|
||||
|
||||
&:hover {
|
||||
color: ${theme.color.textBright};
|
||||
border-color: ${theme.color.borderBrightAlpha};
|
||||
background: rgba(79, 70, 229, 0.14);
|
||||
}
|
||||
`;
|
||||
|
||||
const Row = styled.div`
|
||||
@@ -551,10 +627,87 @@ const Field = styled.div`
|
||||
min-width: 0;
|
||||
`;
|
||||
|
||||
const StudyCard = styled(Card)`
|
||||
background:
|
||||
linear-gradient(180deg, rgba(21, 21, 28, 0.96) 0%, rgba(17, 17, 24, 0.98) 100%);
|
||||
border: 1px solid ${theme.color.borderSoftAlpha};
|
||||
border-radius: 24px;
|
||||
box-shadow: ${theme.shadow.cardElevated};
|
||||
`;
|
||||
|
||||
const FieldFrame = styled.div<{ $invalid?: boolean }>`
|
||||
position: relative;
|
||||
border-radius: 16px;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
transition: background-color 0.2s ease, box-shadow 0.2s ease;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: inherit;
|
||||
padding: 1px;
|
||||
background: ${({ $invalid }) =>
|
||||
$invalid ? theme.color.danger : theme.color.brandGradient};
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
pointer-events: none;
|
||||
-webkit-mask:
|
||||
linear-gradient(#fff 0 0) content-box,
|
||||
linear-gradient(#fff 0 0);
|
||||
-webkit-mask-composite: xor;
|
||||
mask-composite: exclude;
|
||||
}
|
||||
|
||||
&:focus-within {
|
||||
background: rgba(255, 255, 255, 0.015);
|
||||
box-shadow: 0 0 0 4px rgba(79, 70, 229, 0.12);
|
||||
}
|
||||
|
||||
&:focus-within::before {
|
||||
opacity: 1;
|
||||
}
|
||||
`;
|
||||
|
||||
const sharedFieldStyles = css`
|
||||
background: transparent;
|
||||
border: 1px solid ${theme.color.borderSoftAlpha};
|
||||
border-radius: 16px;
|
||||
color: ${theme.color.textBright};
|
||||
min-height: 52px;
|
||||
padding: 14px 16px;
|
||||
|
||||
&::placeholder {
|
||||
color: ${theme.color.textMute};
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-color: transparent;
|
||||
}
|
||||
`;
|
||||
|
||||
const FieldInput = styled(Input)`
|
||||
${sharedFieldStyles}
|
||||
`;
|
||||
|
||||
const FieldSelect = styled(Select)`
|
||||
${sharedFieldStyles}
|
||||
`;
|
||||
|
||||
const FieldTextarea = styled(Textarea)`
|
||||
${sharedFieldStyles}
|
||||
min-height: 132px;
|
||||
resize: vertical;
|
||||
`;
|
||||
|
||||
const ResultGrid = styled.div`
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: ${theme.space.sm};
|
||||
|
||||
@media (max-width: ${theme.breakpoint.mobile}) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
`;
|
||||
|
||||
const ResultChoice = styled.button<{
|
||||
@@ -565,20 +718,20 @@ const ResultChoice = styled.button<{
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
gap: 8px;
|
||||
padding: ${theme.space.md};
|
||||
border-radius: ${theme.radius.md};
|
||||
border-radius: 18px;
|
||||
background: ${({ $active }) =>
|
||||
$active ? theme.color.surfaceHover : theme.color.surface2};
|
||||
$active ? 'rgba(255, 255, 255, 0.05)' : 'rgba(255, 255, 255, 0.025)'};
|
||||
border: 1px solid
|
||||
${({ $active, $tone }) =>
|
||||
$active
|
||||
? $tone === 'success'
|
||||
? theme.color.success
|
||||
? 'rgba(34, 197, 94, 0.55)'
|
||||
: $tone === 'warning'
|
||||
? theme.color.warning
|
||||
: theme.color.danger
|
||||
: theme.color.border};
|
||||
? 'rgba(245, 158, 11, 0.55)'
|
||||
: 'rgba(239, 68, 68, 0.55)'
|
||||
: theme.color.borderSoftAlpha};
|
||||
color: ${({ $active, $tone }) =>
|
||||
$active
|
||||
? $tone === 'success'
|
||||
@@ -591,16 +744,19 @@ const ResultChoice = styled.button<{
|
||||
min-height: 72px;
|
||||
transition:
|
||||
border-color 0.15s ease,
|
||||
background 0.15s ease;
|
||||
background 0.15s ease,
|
||||
transform 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: ${theme.color.accent};
|
||||
transform: translateY(-1px);
|
||||
border-color: ${theme.color.borderBrightAlpha};
|
||||
}
|
||||
`;
|
||||
|
||||
const ResultMark = styled.span`
|
||||
font-size: 22px;
|
||||
font-weight: 800;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
const ResultLabel = styled.span`
|
||||
@@ -613,8 +769,76 @@ const ResultLabel = styled.span`
|
||||
const ButtonRow = styled.div`
|
||||
display: flex;
|
||||
gap: ${theme.space.sm};
|
||||
justify-content: flex-end;
|
||||
align-items: stretch;
|
||||
margin-top: ${theme.space.sm};
|
||||
|
||||
> :first-child {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
> :last-child {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
@media (max-width: ${theme.breakpoint.mobile}) {
|
||||
flex-direction: column-reverse;
|
||||
|
||||
> :first-child,
|
||||
> :last-child {
|
||||
flex: 1 1 auto;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const PrimarySubmit = styled(Button)`
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.14), rgba(255, 255, 255, 0.02)),
|
||||
${theme.color.brandGradient};
|
||||
color: white;
|
||||
box-shadow: ${theme.shadow.glowIndigo};
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
filter: brightness(1.06);
|
||||
box-shadow: ${theme.shadow.glowIndigoStrong};
|
||||
}
|
||||
`;
|
||||
|
||||
const SliderHeader = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: ${theme.space.sm};
|
||||
`;
|
||||
|
||||
const SliderValue = styled.span`
|
||||
font-family: ${theme.font.display};
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: ${theme.color.textBright};
|
||||
`;
|
||||
|
||||
const RangeInput = styled.input`
|
||||
width: 100%;
|
||||
accent-color: ${theme.color.brandIndigo};
|
||||
margin: 6px 0 8px;
|
||||
`;
|
||||
|
||||
const ErrorBox = styled.div`
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
padding: 12px 14px;
|
||||
border-radius: 14px;
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
border: 1px solid rgba(239, 68, 68, 0.22);
|
||||
color: #fca5a5;
|
||||
|
||||
${ErrorText} {
|
||||
margin-top: 0;
|
||||
color: inherit;
|
||||
}
|
||||
`;
|
||||
|
||||
const ProblemList = styled.div`
|
||||
@@ -625,26 +849,31 @@ const ProblemList = styled.div`
|
||||
overflow-y: auto;
|
||||
padding-right: 4px;
|
||||
`;
|
||||
|
||||
const ProblemItem = styled.button<{ $active: boolean }>`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: ${theme.space.sm};
|
||||
padding: ${theme.space.sm} ${theme.space.md};
|
||||
background: ${({ $active }) =>
|
||||
$active ? theme.color.surfaceHover : theme.color.surface2};
|
||||
$active ? 'rgba(79, 70, 229, 0.12)' : 'rgba(255, 255, 255, 0.025)'};
|
||||
border: 1px solid
|
||||
${({ $active }) => ($active ? theme.color.accent : theme.color.border)};
|
||||
border-radius: ${theme.radius.md};
|
||||
${({ $active }) =>
|
||||
$active ? 'rgba(129, 140, 248, 0.42)' : theme.color.borderSoftAlpha};
|
||||
border-radius: 18px;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
border-color 0.15s ease,
|
||||
background 0.15s ease;
|
||||
background 0.15s ease,
|
||||
transform 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: ${theme.color.accent};
|
||||
transform: translateY(-1px);
|
||||
border-color: ${theme.color.borderBrightAlpha};
|
||||
}
|
||||
`;
|
||||
|
||||
const ProblemNum = styled.div`
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
@@ -662,7 +891,7 @@ const ProblemMeta = styled.div`
|
||||
const ProblemTitle = styled.div`
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: ${theme.color.textMain};
|
||||
color: ${theme.color.textBright};
|
||||
`;
|
||||
const ProblemSub = styled.div`
|
||||
font-size: 11px;
|
||||
@@ -670,6 +899,28 @@ const ProblemSub = styled.div`
|
||||
font-family: ${theme.font.mono};
|
||||
`;
|
||||
|
||||
const MappingHint = styled.div<{ $tone?: 'default' | 'warning' }>`
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
padding: 12px 14px;
|
||||
border-radius: 14px;
|
||||
border: 1px solid
|
||||
${({ $tone = 'default' }) =>
|
||||
$tone === 'warning' ? 'rgba(245, 158, 11, 0.22)' : 'rgba(79, 70, 229, 0.2)'};
|
||||
background: ${({ $tone = 'default' }) =>
|
||||
$tone === 'warning' ? 'rgba(245, 158, 11, 0.08)' : 'rgba(79, 70, 229, 0.08)'};
|
||||
color: ${({ $tone = 'default' }) =>
|
||||
$tone === 'warning' ? '#fcd34d' : theme.color.textSub};
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
|
||||
b {
|
||||
color: ${theme.color.textBright};
|
||||
}
|
||||
`;
|
||||
|
||||
const Loading = styled.div`
|
||||
padding: ${theme.space.xl};
|
||||
text-align: center;
|
||||
@@ -679,20 +930,39 @@ const Loading = styled.div`
|
||||
|
||||
const Empty = styled.div`
|
||||
padding: ${theme.space.xxl};
|
||||
border: 1px solid ${theme.color.borderSoftAlpha};
|
||||
border-radius: 24px;
|
||||
background:
|
||||
radial-gradient(circle at top center, rgba(79, 70, 229, 0.12), transparent 40%),
|
||||
linear-gradient(180deg, rgba(21, 21, 28, 0.96) 0%, rgba(17, 17, 24, 0.98) 100%);
|
||||
box-shadow: ${theme.shadow.cardElevated};
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: ${theme.space.md};
|
||||
`;
|
||||
const EmptyEmoji = styled.div`
|
||||
font-size: 56px;
|
||||
|
||||
const EmptyIconWrap = styled.div`
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
border-radius: 22px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.12), rgba(255, 255, 255, 0.03)),
|
||||
${theme.color.brandGradient};
|
||||
color: white;
|
||||
box-shadow: ${theme.shadow.glowIndigoStrong};
|
||||
`;
|
||||
|
||||
const EmptyTitle = styled.h2`
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: ${theme.color.textBright};
|
||||
`;
|
||||
const EmptySub = styled.p`
|
||||
max-width: 360px;
|
||||
font-size: 13px;
|
||||
color: ${theme.color.textSub};
|
||||
`;
|
||||
|
||||
Reference in New Issue
Block a user