diff --git a/accounts/templates/accounts/login.html b/accounts/templates/accounts/login.html index e69de29..b372025 100644 --- a/accounts/templates/accounts/login.html +++ b/accounts/templates/accounts/login.html @@ -0,0 +1,40 @@ +{% extends "base.html" %} +{% load static %} +{% block title %}로그인 - PLAN B{% endblock %} +{% block extra_css %} + +{% endblock %} + +{% block content %} +
+
+
다시 오셨네요
+
계획을 이어서 확인해보세요
+ + {% if form.non_field_errors %} +
{{ form.non_field_errors|striptags }}
+ {% endif %} + +
+ {% csrf_token %} + {% if request.GET.next %}{% endif %} + +
+ + {{ form.username }} +
+ +
+ + {{ form.password }} +
+ + +
+ + +
+
+{% endblock %} \ No newline at end of file diff --git a/accounts/templates/accounts/signup.html b/accounts/templates/accounts/signup.html index e69de29..eacc13e 100644 --- a/accounts/templates/accounts/signup.html +++ b/accounts/templates/accounts/signup.html @@ -0,0 +1,53 @@ +{% extends "base.html" %} +{% load static %} +{% block title %}회원가입 - PLAN B{% endblock %} +{% block extra_css %} + +{% endblock %} + +{% block content %} +
+
+
PLAN B 시작하기
+
시험 계획을 함께 관리해요
+ + {% if form.non_field_errors %} +
{{ form.non_field_errors|striptags }}
+ {% endif %} + +
+ {% csrf_token %} + +
+ + {{ form.email }} + {% if form.email.errors %}
{{ form.email.errors|striptags }}
{% endif %} +
+ +
+ + {{ form.nickname }} + {% if form.nickname.errors %}
{{ form.nickname.errors|striptags }}
{% endif %} +
+ +
+ + {{ form.password1 }} + {% if form.password1.errors %}
{{ form.password1.errors|striptags }}
{% endif %} +
+ +
+ + {{ form.password2 }} + {% if form.password2.errors %}
{{ form.password2.errors|striptags }}
{% endif %} +
+ + +
+ + +
+
+{% endblock %} \ No newline at end of file diff --git a/exams/templates/exams/_topbar.html b/exams/templates/exams/_topbar.html new file mode 100644 index 0000000..6912b03 --- /dev/null +++ b/exams/templates/exams/_topbar.html @@ -0,0 +1,11 @@ +
+ + PLAN B + +
+ {% csrf_token %} + +
+
diff --git a/exams/templates/exams/available_time_form.html b/exams/templates/exams/available_time_form.html index e69de29..570bb2e 100644 --- a/exams/templates/exams/available_time_form.html +++ b/exams/templates/exams/available_time_form.html @@ -0,0 +1,174 @@ +{% extends "base.html" %} +{% load static exam_extras %} +{% block title %} +가용시간 입력 - PLAN B +{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block content %} +{% include "exams/_topbar.html" %} +{% get_calendar_grid period.id as calendar_days %} + +
+
+ PLAN B +  ›  가용시간 입력 +
+
+ 가용시간 입력하기 +
+
+ 하루 전체에서 공부 가능한 시간을 날짜별로 입력해주세요 +
+ + {% if formset.non_form_errors %} +
+ {{ formset.non_form_errors|striptags }} +
+ {% endif %} + +
+ {% csrf_token %} + {{ formset.management_form }} + +
+ {% for form in formset %} +
+ {{ form.id }} + {{ form.date }} + {{ form.hours }} + {{ form.minutes }} +
+ {% endfor %} +
+ +
+
+
+ {{ period.start_date|date:"n월 j일" }} - + {{ period.end_date|date:"n월 j일" }} +
+ +
+
+
+
+
+
+
+
+ + {% for day in calendar_days %} + {% if forloop.first %} + {% if day.is_exam_day %} + + {% else %} + + {% endif %} + {% else %} + {% if day.is_exam_day %} + + {% else %} + + {% endif %} + {% endif %} + {% endfor %} +
+
+ +
+
+ 날짜를 선택하세요 +
+
선택한 날짜
+ +
전체 가용 시간
+ +
분 단위로 입력
+ + +
+
+ + +
+
+ + +{% endblock %} \ No newline at end of file diff --git a/exams/templates/exams/material_detail.html b/exams/templates/exams/material_detail.html index e69de29..599e8c4 100644 --- a/exams/templates/exams/material_detail.html +++ b/exams/templates/exams/material_detail.html @@ -0,0 +1,210 @@ +{% extends "base.html" %} +{% load static %} +{% block title %} +{{ material.title }} - PLAN B +{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block content %} +{% include "exams/_topbar.html" %} + +
+
+ {{ material.exam.subject_name }}  ›  {{ material.title }} +
+
+ {{ material.title }} +
+
+ {{ material.get_material_type_display }} +
+ + {% if material.material_type == 'pdf' %} +
+ + + {% if material.status == 'completed' %} + 추출 완료 +
+ {{ material.extracted_text|truncatechars:600 }} +
+ + {% elif material.status == 'failed' %} + 추출 실패 +
+ {{ material.error_message }} +
+
+ {% csrf_token %} + +
+ + {% elif material.status == 'processing' %} + 추출 중... + + {% else %} + + 대기 중 + + {% endif %} +
+ {% endif %} + + {% if material.status == 'completed' %} +
+ + + {% if material.analysis_status == 'completed' %} + 분석 완료 + + 학습 작업 확인하러 가기 + + + {% elif material.analysis_status == 'processing' %} + 분석 중... + + {% elif material.analysis_status == 'failed' %} + 분석 실패 +
+ {{ material.analysis_error_message }} +
+
+ 재시도 {{ material.analysis_retry_count }}회 진행됨 +
+
+ {% csrf_token %} + +
+ + {% else %} + + 대기 중 + + {% endif %} +
+ {% endif %} + +
+ {% csrf_token %} + +
+
+ +
+
+
+
+ 처리 중이에요 +
+
+ 잠시만 기다려주세요 +
+
+
+ 생각보다 오래 걸리고 있어요. + 백엔드 처리가 아직 안 끝났을 수 있어요. +
+ +
+
+
+ +
+ {% csrf_token %} +
+ +
+ {% csrf_token %} +
+ + +{% endblock %} \ No newline at end of file diff --git a/exams/templates/exams/material_form.html b/exams/templates/exams/material_form.html index e69de29..d76f248 100644 --- a/exams/templates/exams/material_form.html +++ b/exams/templates/exams/material_form.html @@ -0,0 +1,143 @@ +{% extends "base.html" %} +{% load static exam_extras %} +{% block title %} +시험범위 등록 - PLAN B +{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block content %} +{% include "exams/_topbar.html" %} +{% get_period_exams exam.exam_period_id as period_exams %} + +
+
+ PLAN B  ›  시험범위 등록 +
+
+ 시험범위 등록하기 +
+
+ 과목별 시험범위를 알려주세요 +
+ +
+ {% for e in period_exams %} + + {{ e.subject_name }} + + {% endfor %} +
+ + {% if form.non_field_errors %} +
+ {{ form.non_field_errors|striptags }} +
+ {% endif %} + +
+
+ + +
+ +
+ {% csrf_token %} + + + + + {% if form.file.errors %} +
+ {{ form.file.errors|striptags }} +
+ {% endif %} + +
+ + {{ form.file }} +
+ 텍스트 추출이 가능한 PDF만 지원돼요 (최대 10MB) +
+
+ + + + +
+
+
+ +
+
+
+
학습 작업을 분석하고 있어요
+
잠시만 기다려주세요
+
+
+ + +{% endblock %} \ No newline at end of file diff --git a/exams/templates/exams/period_detail.html b/exams/templates/exams/period_detail.html index e69de29..2f96090 100644 --- a/exams/templates/exams/period_detail.html +++ b/exams/templates/exams/period_detail.html @@ -0,0 +1,95 @@ +{% extends "base.html" %} +{% load static %} +{% block title %} +{{ period.title }} - PLAN B +{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block content %} +{% include "exams/_topbar.html" %} + +
+
+ PLAN B +  ›  {{ period.title }} +
+ +
+
+
{{ period.title }}
+
+ {{ period.start_date|date:"n월 j일" }} ~ + {{ period.end_date|date:"n월 j일" }} +
+
+ + 기간 수정 + +
+ +
+
+
날짜별 공부 가능시간
+
+ 총 {{ available_times|length }}일 등록됨 +
+
+ + 가능시간 입력하기 + +
+ +
+
+ 과목 ({{ exams|length }}) +
+ + + 과목 추가 + +
+ + {% if not exams %} +
+ 아직 등록된 과목이 없어요. 먼저 과목을 추가해주세요. +
+ {% endif %} + + {% for exam in exams %} +
+
+
+
{{ exam.subject_name }}
+
+ 시험일 {{ exam.exam_date|date:"n월 j일" }} · + 우선순위 {{ exam.get_priority_display }} +
+
+
+ + 수정 + +
+ {% csrf_token %} + +
+
+
+ + +
+ {% endfor %} +
+{% endblock %} \ No newline at end of file diff --git a/exams/templates/exams/period_form.html b/exams/templates/exams/period_form.html index e69de29..a8bdff7 100644 --- a/exams/templates/exams/period_form.html +++ b/exams/templates/exams/period_form.html @@ -0,0 +1,60 @@ +{% extends "base.html" %} +{% load static %} +{% block title %}시험기간 등록 - PLAN B{% endblock %} +{% block extra_css %} + +{% endblock %} + +{% block content %} +
+
+ {% if period %}시험기간 수정하기{% else %}시험기간 등록하기{% endif %} +
+
이 기간 동안의 계획을 세울게요
+ + {% if form.non_field_errors %} +
{{ form.non_field_errors|striptags }}
+ {% endif %} + +
+
+ {% csrf_token %} + +
+ + {{ form.title }} + {% if form.title.errors %}
{{ form.title.errors|striptags }}
{% endif %} +
+ +
+
+ + {{ form.start_date }} +
+
+ + {{ form.end_date }} +
+
+ {% if form.start_date.errors or form.end_date.errors %} +
{{ form.start_date.errors|striptags }} {{ form.end_date.errors|striptags }}
+ {% endif %} + + +
+
+ + {% if period %} +
+ {% csrf_token %} + +
+ {% endif %} +
+{% endblock %} \ No newline at end of file diff --git a/exams/templates/exams/period_list.html b/exams/templates/exams/period_list.html index e69de29..52c6b66 100644 --- a/exams/templates/exams/period_list.html +++ b/exams/templates/exams/period_list.html @@ -0,0 +1,61 @@ +{% extends "base.html" %} +{% load static %} +{% block title %}시험기간 목록 - PLAN B{% endblock %} +{% block extra_css %} +{% endblock %} + +{% block content %} +{% include "exams/_topbar.html" %} + +
+
내 시험기간
+
진행 중인 시험기간을 이어가거나 새로 시작해보세요
+ + {% if not has_periods %} +
+
아직 등록된 시험기간이 없어요
+ 첫 시험기간 만들기 +
+ {% else %} +
+ + 새 시험기간 +
+ + {% if ongoing_periods %} +
진행 중
+ {% for period in ongoing_periods %} + +
+
+
{{ period.title }}
+
{{ period.start_date|date:"n/j" }} ~ + {{ period.end_date|date:"n/j" }}
+
+ + {{ period.get_status_display }} + +
+
+ {% endfor %} + {% endif %} + + {% if completed_periods %} +
지난 시험기간
+ {% for period in completed_periods %} + +
+
+
{{ period.title }}
+
{{ period.start_date|date:"n/j" }} ~ + {{ period.end_date|date:"n/j" }}
+
+ {{ period.get_status_display + }} +
+
+ {% endfor %} + {% endif %} + {% endif %} +
+{% endblock %} \ No newline at end of file diff --git a/exams/templates/exams/subject_form.html b/exams/templates/exams/subject_form.html index e69de29..dec461e 100644 --- a/exams/templates/exams/subject_form.html +++ b/exams/templates/exams/subject_form.html @@ -0,0 +1,101 @@ +{% extends "base.html" %} +{% load static exam_extras %} +{% block title %} +과목 등록 - PLAN B +{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block content %} +{% include "exams/_topbar.html" %} +{% get_period_exams period.id as period_exams %} + +
+
+ + PLAN B + +  ›  과목 등록 +
+
+ 과목 등록하기 +
+
+ 시험일과 우선순위를 입력하세요 +
+ + {% if form.non_field_errors %} +
+ {{ form.non_field_errors|striptags }} +
+ {% endif %} + +
+
+
+ {% csrf_token %} + +
+ + {{ form.subject_name }} + {% if form.subject_name.errors %} +
+ {{ form.subject_name.errors|striptags }} +
+ {% endif %} +
+ +
+ + {{ form.exam_date }} + {% if form.exam_date.errors %} +
+ {{ form.exam_date.errors|striptags }} +
+ {% endif %} +
+ +
+ + {{ form.priority }} +
+ + +
+
+ +
+
+ 등록된 과목 {{ period_exams|length }}개 +
+ {% for exam in period_exams %} +
+ + {{ exam.priority|slice:":1"|upper }} + + + {{ exam.subject_name }} + + + {{ exam.exam_date|date:"n/j" }} + +
+ {% empty %} +
+ 아직 등록된 과목이 없어요 +
+ {% endfor %} +
+
+
+{% endblock %} \ No newline at end of file diff --git a/exams/templates/exams/task_form.html b/exams/templates/exams/task_form.html index e69de29..680c96a 100644 --- a/exams/templates/exams/task_form.html +++ b/exams/templates/exams/task_form.html @@ -0,0 +1,91 @@ +{% extends "base.html" %} +{% load static %} +{% block title %} +작업 추가 - PLAN B +{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block content %} +{% include "exams/_topbar.html" %} + +
+
+ + {{ exam.subject_name }} 학습 작업 + +  ›  추가 +
+
+ 학습 작업 추가하기 +
+
+ 직접 작업을 추가할 수 있어요 +
+ + {% if form.non_field_errors %} +
+ {{ form.non_field_errors|striptags }} +
+ {% endif %} + +
+
+ {% csrf_token %} + +
+ + {{ form.unit_name }} +
+ +
+ + {{ form.title }} + {% if form.title.errors %} +
+ {{ form.title.errors|striptags }} +
+ {% endif %} +
+ +
+ + {{ form.task_type }} +
+ +
+
+ + {{ form.importance }} +
+
+ + {{ form.depth }} +
+
+ + {{ form.difficulty }} +
+
+ + +
+
+
+{% endblock %} \ No newline at end of file diff --git a/exams/templates/exams/task_review.html b/exams/templates/exams/task_review.html index e69de29..fb09c07 100644 --- a/exams/templates/exams/task_review.html +++ b/exams/templates/exams/task_review.html @@ -0,0 +1,218 @@ +{% extends "base.html" %} +{% load static exam_extras %} +{% block title %} +학습 작업 - PLAN B +{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block content %} +{% include "exams/_topbar.html" %} +{% get_period_exams exam.exam_period_id as period_exams %} + +
+
+ PLAN B  ›  학습 작업 +
+ +
+
+
+ 학습 작업 +
+
+ {{ exam.subject_name }} · 총 {{ formset.forms|length }}개 +
+
+ + + 작업 추가하기 + +
+ +
+
+
+
과목
+ {% for e in period_exams %} + + {{ e.subject_name }} + + {{ e.study_tasks.count }}개 + + + {% endfor %} +
+
+ +
+ {% if formset.non_form_errors %} +
+ {{ formset.non_form_errors|striptags }} +
+ {% endif %} + +
+ {% csrf_token %} + {{ formset.management_form }} + + {% if not formset.forms %} +
+ 아직 학습 작업이 없어요. 시험범위 등록 화면에서 + AI 분석을 먼저 실행하거나, 직접 추가해주세요. +
+ {% endif %} + + {% for form in formset %} +
+ {{ form.id }} + +
+
+ {{ form.instance.unit_name|default:"단원 미지정" }} +
+ {% if form.instance.is_confirmed %} + 확정됨 + {% endif %} +
+ + {# 단원명: 읽기 전용 — 값은 그대로 formset에 전송되지만 수정은 못 하게 처리 #} + + +
+ + {{ form.title }} + {% if form.title.errors %} +
+ {{ form.title.errors|striptags }} +
+ {% endif %} +
+ +
+
+ + {{ form.task_type }} +
+
+ + {{ form.importance }} +
+
+ + {{ form.depth }} +
+
+ + {{ form.difficulty }} +
+
+ + + + {% if form.DELETE %} +
+ {{ form.DELETE }} +
+ {% endif %} +
+ {% endfor %} + + {% if formset.forms %} +
+ + +
+ {% endif %} +
+
+
+
+ + + + +{% endblock %} \ No newline at end of file diff --git a/exams/templatetags/__init__.py b/exams/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/exams/templatetags/exam_extras.py b/exams/templatetags/exam_extras.py new file mode 100644 index 0000000..fc64c73 --- /dev/null +++ b/exams/templatetags/exam_extras.py @@ -0,0 +1,62 @@ +""" +FE1(신예원) 전용 템플릿 태그. + +여기 있는 이유: +- subject_create, material_create, task_review 뷰가 '같은 시험기간의 다른 과목 목록'을 + context로 안 내려주고 있어서, Figma처럼 "등록된 과목 리스트"나 "과목 탭 전환"을 + 보여주려면 어딘가에서 그 데이터를 가져와야 함. +- views.py를 직접 고치는 대신(BE2 담당 파일), 프론트 표시 목적으로만 쓰는 조회 로직을 + 템플릿 태그로 분리해둠. 나중에 백엔드 뷰가 정식으로 context를 내려주게 되면 + 이 템플릿 태그 호출부만 지우면 되니까 되돌리기 쉬움. +- 여기서는 절대 데이터를 만들거나 바꾸지 않음(읽기 전용 조회만). +""" +from datetime import timedelta + +from django import template +from exams.models import Exam, AvailableTime, ExamPeriod + +register = template.Library() + + +@register.simple_tag +def get_period_exams(period_id): + """해당 시험기간에 등록된 과목 목록 (시험일 순).""" + return Exam.objects.filter(exam_period_id=period_id).order_by('exam_date') + + +@register.simple_tag +def get_calendar_grid(period_id): + """ + 가용시간 입력 화면의 캘린더 그리드용 데이터. + + 시험기간 시작일~종료일의 모든 날짜를 순서대로 돌면서, + - 시험일이면 is_exam_day=True (AvailableTime에 없는 날) + - 아니면 AvailableTime formset 안에서 몇 번째(index)에 해당하는지 계산 + + formset은 AvailableTime.objects.filter(exam_period=period).order_by('date') + 순서와 동일하다고 가정함 (모델 Meta.ordering = ['date']). + """ + period = ExamPeriod.objects.get(pk=period_id) + exam_dates = set( + Exam.objects.filter(exam_period_id=period_id).values_list('exam_date', flat=True) + ) + available_rows = list( + AvailableTime.objects.filter(exam_period_id=period_id) + .order_by('date') + .values_list('date', 'available_minutes') + ) + date_to_index = {d: i for i, (d, _) in enumerate(available_rows)} + date_to_minutes = {d: m for d, m in available_rows} + + days = [] + current = period.start_date + while current <= period.end_date: + days.append({ + 'date': current, + 'weekday': current.isoweekday(), # 1=월 ... 7=일 + 'is_exam_day': current in exam_dates, + 'formset_index': date_to_index.get(current), + 'has_value': date_to_minutes.get(current, 0) > 0, + }) + current += timedelta(days=1) + return days \ No newline at end of file diff --git a/static/css/components.css b/static/css/components.css new file mode 100644 index 0000000..07b2983 --- /dev/null +++ b/static/css/components.css @@ -0,0 +1,650 @@ +/* PLAN B - 공통 컴포넌트 + forms.py 의 widget class(form-control, form-select 등)와 이름을 맞춰뒀음. + tokens.css 의 변수만 사용 — 새 색상 임의로 추가하지 말 것. */ + +.btn{ + display:inline-flex; align-items:center; justify-content:center; gap:10px; + height:56px; padding:0 28px; border-radius:16px; + background:var(--neutral11); color:var(--neutral1); font-size:15px; font-weight:600; + border:none; cursor:pointer; + box-shadow:0px 2px 4px rgba(0,0,0,.2); +} +.btn:hover{ background:var(--neutral10); } +.btn:disabled{ background:var(--neutral6); cursor:not-allowed; box-shadow:none; } +.btn.btn-brand{ background:var(--brand4); } +.btn.btn-brand:hover{ background:var(--brand5); } +.btn.btn-ghost{ background:var(--neutral1); color:var(--neutral11); border:2px solid var(--neutral4); box-shadow:none; } +.btn.btn-block{ width:100%; } + +.card{ + background:var(--neutral1); border:2px solid var(--neutral4); border-radius:24px; + padding:28px 30px; + box-shadow:0px 1px 2px rgba(0,0,0,.3), 0px 1px 3px 1px rgba(0,0,0,.15); +} + +.form-group{ margin-bottom:18px; } +.form-label{ display:block; font-size:13.5px; font-weight:600; color:var(--neutral9); margin-bottom:7px; } + +.form-control, .form-select, .textarea-input{ + width:100%; height:56px; padding:0 18px; border-radius:15px; + border:none; background:var(--neutral1); + box-shadow:2px 2px 5px rgba(0,0,0,.12); + font-family:var(--ui); font-size:15px; color:var(--neutral10); +} +.textarea-input{ height:auto; padding:14px 18px; resize:vertical; } +.form-control:focus, .form-select:focus, .textarea-input:focus{ + outline:2px solid var(--brand4); outline-offset:1px; +} +.file-input{ padding:10px 14px; height:auto; background:var(--neutral2); } +.number-input{ text-align:center; } + +.field-error{ color:var(--judge3); font-size:12px; margin-top:5px; } +.form-error-box{ + background:var(--judge3-bg); border:1px solid var(--judge3-line); color:var(--judge3); + border-radius:10px; padding:10px 14px; font-size:13px; margin-bottom:16px; +} + +.page-title{ font-size:34px; font-weight:700; color:var(--neutral11); margin-bottom:8px; } +.page-sub{ font-size:14px; color:var(--neutral7); margin-bottom:22px; } + +/* 판정 뱃지 (가능/위험/불가능) — planner/feasibility 등에서 재사용 */ +.badge{ display:inline-flex; align-items:center; gap:6px; font-size:13px; font-weight:600; padding:7px 18px; border-radius:20px; } +.badge::before{ content:""; width:7px; height:7px; border-radius:50%; } +.badge.badge-possible{ background:var(--judge1-bg); color:var(--judge1); border:1px solid var(--judge1-line); } +.badge.badge-possible::before{ background:var(--judge1); } +.badge.badge-risky{ background:var(--judge2-bg); color:var(--judge2); border:1px solid var(--judge2-line); } +.badge.badge-risky::before{ background:var(--judge2); } +.badge.badge-impossible{ background:var(--judge3-bg); color:var(--judge3); border:1px solid var(--judge3-line); } +.badge.badge-impossible::before{ background:var(--judge3); } + +/* 과목 우선순위 원형 배지 (H/M/L) — Figma 브랜드 블루 계층 그대로 사용 */ +.priority-dot{ + width:26px; height:26px; border-radius:50%; flex-shrink:0; + display:flex; align-items:center; justify-content:center; + font-size:10.5px; font-weight:800; +} +.priority-dot.priority-high{ background:var(--brand4); color:var(--neutral1); } +.priority-dot.priority-medium{ background:var(--brand3); color:var(--neutral10); } +.priority-dot.priority-low{ background:var(--brand2); color:var(--neutral9); } + +/* 탭바 — 실제로는 페이지 이동 링크()지만 시각적으로는 탭처럼 보이게 */ +.tabbar{ display:flex; gap:6px; margin-bottom:14px; flex-wrap:wrap; } +.tabbar a.tab{ + padding:8px 16px; border-radius:9px; background:var(--neutral2); color:var(--neutral8); + font-size:12px; font-weight:600; text-decoration:none; display:inline-block; +} +.tabbar a.tab.active{ background:var(--neutral11); color:var(--neutral1); } +.auth-wrap{ min-height:100vh; display:flex; align-items:center; justify-content:center; padding:20px; } +.auth-card{ width:380px; } +.auth-link{ text-align:center; font-size:13px; color:var(--neutral8); margin-top:16px; } +.auth-link a{ color:var(--brand4); font-weight:700; text-decoration:none; } + +/* 2열 레이아웃 (subject_form 등) */ +.two-col{ display:flex; gap:20px; align-items:flex-start; } +.two-col > *{ flex:1; min-width:0; } +.grow{ flex:1; min-width:0; } + +/* 리스트 카드 한 줄 (등록된 과목 등) */ +.list-card{ + display:flex; align-items:center; gap:10px; + padding:11px 14px; border-radius:11px; background:var(--neutral2); + margin-bottom:8px; font-size:12.5px; +} + +/* 범용 필/탭 배지 — .tabbar 밖에서 단독으로도 씀 (task_review의 과목/깊이 태그 등) */ +.tab{ + padding:9px 20px; border-radius:14px; background:var(--neutral4); color:var(--neutral8); + font-size:13px; font-weight:600; display:inline-block; +} +.tab.active{ background:var(--neutral11); color:var(--neutral1); } + +/* 세그먼트 선택형 (중요도/깊이/난이도) — 작업 수정 모달에서 사용 */ +.segment{ display:flex; gap:6px; } +.segment .opt{ + flex:1; text-align:center; padding:9px 0; border-radius:9px; + font-size:11.5px; font-weight:700; background:var(--neutral2); color:var(--neutral8); + border:none; cursor:pointer; font-family:var(--ui); +} +.segment .opt.sel{ background:var(--neutral11); color:var(--neutral1); } + +/* 모달 — 작업 수정 화면에서 사용 */ +.modal-backdrop{ + position:fixed; inset:0; background:rgba(20,25,35,.35); + display:none; align-items:center; justify-content:center; z-index:50; +} +.modal-backdrop.open{ display:flex; } +.modal{ + width:480px; max-width:92vw; background:var(--neutral1); border-radius:18px; + padding:30px 30px 26px; box-shadow:0 10px 40px rgba(0,0,0,.2); +} + +/* 학습 작업 확인 화면 전용 */ +.task-review-wrap { + max-width: 1080px; + margin: 0 auto; + padding-bottom: 60px; +} +.crumb-text { + font-size: 11.5px; + color: var(--neutral7); + margin-bottom: 10px; +} +.task-review-header { + display: flex; + justify-content: space-between; + align-items: flex-end; + margin-bottom: 24px; +} +.task-review-body { + display: flex; + gap: 24px; + align-items: flex-start; +} +.task-nav-col { + flex: 0 0 200px; +} +.task-nav-card { + padding: 18px; +} +.task-nav-title { + font-size: 11px; + font-weight: 700; + color: var(--neutral7); + letter-spacing: 0.03em; + margin-bottom: 10px; +} +.task-list-col { + flex: 1; + min-width: 0; +} +.task-empty-card { + text-align: center; + padding: 48px 20px; + color: var(--neutral7); + font-size: 13px; +} +.task-item-card { + margin-bottom: 14px; + padding: 24px 26px; + border: 2px solid var(--neutral4); + border-left: 4px solid var(--neutral5); +} +.task-item-card.priority-high { + border-left-color: var(--brand4); +} +.task-item-top { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8px; +} +.task-unit-label { + font-size: 11px; + font-weight: 700; + color: var(--neutral7); + letter-spacing: 0.02em; +} +.task-readonly-input { + background: var(--neutral3) !important; + color: var(--neutral8) !important; + cursor: not-allowed; + margin-bottom: 14px; +} +.task-field-group { + margin-bottom: 14px; +} +.task-select-row { + display: flex; + gap: 10px; + margin-bottom: 16px; +} +.task-select-item { + flex: 1; + min-width: 0; +} +.task-item-footer { + display: flex; + justify-content: space-between; + align-items: center; + padding-top: 14px; + border-top: 1px solid var(--neutral4); +} +.task-time-pill { + background: var(--neutral2); + padding: 8px 18px; + border-radius: 20px; + font-size: 15px; + font-weight: 700; + color: var(--neutral10); +} +.task-action-row { + display: flex; + gap: 10px; + margin-top: 4px; +} +.task-action-row .btn { + flex: 1; +} +.task-confirm-btn { + background: var(--neutral11); + color: var(--neutral1); +} +.task-delete-hidden { + display: none; +} +.delete-confirm-box { + width: 360px; + text-align: center; +} +.delete-confirm-title { + font-size: 15px; + font-weight: 700; + margin-bottom: 8px; +} +.delete-confirm-body { + font-size: 13px; + color: var(--neutral7); + margin-bottom: 20px; +} +.delete-confirm-actions { + display: flex; + gap: 8px; +} +.delete-confirm-actions .btn { + flex: 1; +} + +/* 시험범위 등록 화면 - Figma 6페이지 레이아웃 맞춤 */ +.material-form-wrap { + max-width: 900px; + margin: 0 auto; + padding-bottom: 60px; +} +.material-form-card { + padding: 32px; +} +.material-upload-panel input[type="file"] { + display: none; +} +.material-upload-box { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 10px; + min-height: 280px; + border: 1.5px dashed var(--neutral5); + border-radius: 14px; + background: var(--neutral2); + cursor: pointer; +} +.material-upload-icon { + font-size: 30px; + color: var(--neutral6); +} +.material-upload-text { + font-size: 13px; + color: var(--neutral8); +} +.material-upload-hint { + font-size: 11.5px; + color: var(--neutral7); + text-align: center; + margin-top: 10px; +} +#text-panel textarea { + width: 100%; + min-height: 280px; + border: 1.5px dashed var(--neutral5); + border-radius: 14px; + background: var(--neutral2); + padding: 16px; + font-family: var(--ui); + font-size: 13px; + color: var(--neutral10); + resize: vertical; +} +.material-submit-btn { + margin-top: 20px; + height: 48px; +} + +/* 학습자료 상세(추출/분석 자동 진행) 화면 */ +.material-detail-wrap { + max-width: 560px; + margin: 0 auto; + padding-bottom: 60px; +} +.section-label { + font-size: 13px; + font-weight: 700; + margin-bottom: 10px; +} +.extracted-text-box { + margin-top: 10px; + max-height: 140px; + overflow-y: auto; + font-size: 12px; + color: var(--neutral8); + background: var(--neutral2); + border-radius: 8px; + padding: 10px 12px; + white-space: pre-wrap; +} +.delete-material-btn { + color: var(--judge3); + border-color: var(--judge3-line); +} +.hidden-auto-form { + display: none; +} +.auto-loading-overlay { + display: none; + position: fixed; + inset: 0; + background: rgba(20, 25, 35, 0.4); + align-items: center; + justify-content: center; + z-index: 60; +} +.auto-loading-overlay.open { + display: flex; +} +.auto-loading-box { + width: 320px; + text-align: center; +} +.auto-loading-spinner { + width: 44px; + height: 44px; + border-radius: 50%; + border: 4px solid var(--neutral4); + border-top-color: var(--brand4); + margin: 0 auto 16px; + animation: spin 0.9s linear infinite; +} +@keyframes spin { + to { transform: rotate(360deg); } +} +.auto-loading-text { + font-size: 13px; + font-weight: 700; +} +.auto-loading-sub { + font-size: 11.5px; + color: var(--neutral7); + margin-top: 4px; +} + +/* 가용시간 입력 화면 (Figma 5페이지 - 캘린더 + 사이드패널) */ +.avail-wrap { + max-width: 900px; + margin: 0 auto; + padding-bottom: 60px; +} +.avail-body { + display: flex; + gap: 20px; + align-items: flex-start; +} +.avail-cal-col { + flex: 1.6; +} +.avail-cal-title { + font-size: 14px; + font-weight: 700; + margin-bottom: 4px; +} +.avail-cal-grid { + display: grid; + grid-template-columns: repeat(7, 1fr); + gap: 8px; + margin-top: 14px; +} +.avail-weekday { + font-size: 11px; + color: var(--neutral7); + text-align: center; + padding-bottom: 2px; +} +.avail-day { + height: 46px; + border-radius: 9px; + background: var(--neutral2); + border: none; + font-size: 12px; + color: var(--neutral9); + font-family: var(--mono); + display: flex; + align-items: flex-start; + justify-content: flex-start; + padding: 6px; + cursor: pointer; + position: relative; +} +.avail-day.exam-day { + background: var(--neutral3); + color: var(--neutral6); + cursor: not-allowed; +} +.avail-day.has-value::after { + content: ""; + position: absolute; + bottom: 5px; + right: 5px; + width: 5px; + height: 5px; + border-radius: 50%; + background: var(--judge1); +} +.avail-day.sel { + background: var(--brand4); + color: var(--neutral1); +} +.avail-side-col { + flex: 1; + position: sticky; + top: 16px; +} +.avail-side-col.disabled { + opacity: 0.45; + pointer-events: none; +} +.avail-side-date { + font-size: 15px; + font-weight: 700; + margin-bottom: 2px; +} +.avail-side-caption { + font-size: 11.5px; + color: var(--neutral7); + margin-bottom: 16px; +} +.avail-side-hint { + font-size: 11px; + color: var(--neutral7); + margin-top: 4px; +} +.avail-save-btn { + margin-top: 14px; +} +.avail-next-btn { + margin-top: 18px; +} +.formset-hidden { + display: none; +} + +/* period_detail.html 전용 (시험기간 상세 허브 화면) */ +.period-detail-wrap { + max-width: 760px; + margin: 0 auto; + padding-bottom: 60px; +} +.period-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 22px; +} +.period-date-range { + margin-bottom: 0; +} +.period-avail-card { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; +} +.period-avail-title { + font-size: 13.5px; + font-weight: 700; + color: var(--neutral11); +} +.period-avail-caption { + font-size: 12px; + color: var(--neutral7); + margin-top: 3px; +} +.period-subject-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 12px; +} +.period-subject-count { + font-size: 14px; + font-weight: 700; +} +.period-add-subject-btn { + height: 36px; + padding: 0 14px; + font-size: 12.5px; +} +.period-empty-card { + text-align: center; + padding: 36px 20px; + color: var(--neutral7); + font-size: 13px; +} +.period-subject-card { + margin-bottom: 12px; +} +.period-subject-top { + display: flex; + justify-content: space-between; + align-items: flex-start; +} +.period-subject-name { + font-size: 14.5px; + font-weight: 700; + color: var(--neutral11); +} +.period-subject-meta { + font-size: 12px; + color: var(--neutral7); + margin-top: 3px; +} +.period-subject-actions { + display: flex; + gap: 6px; +} +.period-inline-form { + margin: 0; +} +.period-small-btn { + height: 32px; + padding: 0 10px; + font-size: 11px; +} +.period-delete-btn { + color: var(--judge3); + border-color: var(--judge3-line); +} +.period-subject-links { + display: flex; + gap: 8px; + margin-top: 14px; + padding-top: 14px; + border-top: 1px solid var(--neutral4); +} +.period-link-btn { + flex: 1; + justify-content: center; + font-size: 12px; +} + +/* 시험범위 등록 - 에러 강조 */ +.material-upload-box.error { + border-color: var(--judge3); + background: var(--judge3-bg); +} +.material-upload-error { + margin-bottom: 14px; +} + +/* task_form.html 전용 */ +.task-form-wrap { + max-width: 480px; + margin: 20px auto; + padding-bottom: 60px; +} +.task-form-select-row { + display: flex; + gap: 10px; +} +.task-form-select-row .form-group { + flex: 1; +} + +/* 오래 걸릴 때 안내 (안전장치) */ +.auto-loading-stuck { + display: none; + margin-top: 16px; + padding-top: 16px; + border-top: 1px solid var(--neutral4); +} +.auto-loading-stuck.show { + display: block; +} +.auto-loading-stuck-text { + font-size: 11.5px; + color: var(--neutral7); + margin-bottom: 10px; +} + +/* 학습 작업 화면 좌측 과목 리스트 - 진짜 버튼처럼 */ +.nav-row-link { + display: flex; + justify-content: space-between; + align-items: center; + padding: 13px 14px; + border-radius: 10px; + font-size: 13.5px; + font-weight: 600; + text-decoration: none; + color: var(--neutral10); + background: var(--neutral2); + margin-bottom: 8px; + border: 1px solid transparent; +} +.nav-row-link:hover { + background: var(--brand1); + border-color: var(--brand3); +} +.nav-row-link.active { + background: var(--neutral11); + color: var(--neutral1); + font-weight: 700; +} +.nav-row-link.active .num { + color: var(--neutral1) !important; +} + +/* 컴팩트 타이틀 (인증 화면, 학습자료 상세처럼 작은 카드용) */ +.page-title-sm { + font-size: 26px; + font-weight: 700; + color: var(--neutral11); + margin-bottom: 8px; +} \ No newline at end of file