@charset "utf-8";

/* ============================================================================
   Tier 3 of 4 — reset and base element styling

   What a bare HTML element looks like before any class is applied. A page
   that uses no class at all should still be readable, which is what makes
   the generated starter page presentable without anyone writing CSS.

   Ownership: user-owned. The template ships this file as a working starter and
   the generator writes it only when it is absent, so a project's own edits
   survive every regeneration. The flip side: a fix made here reaches new
   projects only. To take it into an existing project, copy the change across
   by hand, or delete the project's copy and regenerate.
   ========================================================================= */

*,
*::before,
*::after {
    box-sizing: border-box;
}

body,
h1, h2, h3, h4, h5, h6,
p, figure, blockquote, dl, dd,
ul, ol {
    margin: 0;
}

body {
    min-height: 100vh;
    font-family: var(--font-base);
    font-size: var(--fs-body);
    font-weight: var(--fw-regular);
    line-height: var(--lh-body);
    color: var(--color-text);
    background-color: var(--color-bg);
    -webkit-text-size-adjust: 100%;
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
}

/*
 * The splash page content is user-owned, but the entry shell is generated.
 * Keep the legacy `.index_wrap` markup centered without making a generic
 * `.index_wrap` class affect ordinary application pages.
 */
#splash-entry {
    display: flex;
    padding: 0;
    min-height: 100vh;
    min-height: 100dvh;
}

#splash-entry .index_wrap {
    display: flex;
    flex: 1 1 auto;
    align-items: center;
    justify-content: center;
    width: 100%;
}

#splash-entry .index_wrap img {
    width: 60%;
    height: auto;
}

h1, h2, h3, h4, h5, h6 {
    font-family: var(--font-display);
    font-weight: var(--fw-semibold);
    line-height: var(--lh-snug);
    letter-spacing: var(--ls-tight);
    color: var(--color-text);
}

h1 { font-size: var(--fs-page-title); }
h2 { font-size: var(--fs-title); }
h3 { font-size: var(--fs-label); }
h4, h5, h6 { font-size: var(--fs-body); }

p { line-height: var(--lh-relaxed); }

p + p,
p + ul,
p + ol,
ul + p,
ol + p {
    margin-top: var(--space-md);
}

small { font-size: var(--fs-caption); }

strong, b { font-weight: var(--fw-semibold); }

hr {
    height: 0;
    margin: var(--space-lg) 0;
    border: 0;
    border-top: var(--size-hairline) solid var(--color-line);
}

/* ---- links --------------------------------------------------------------
   Underline stays on by default. Removing it is a design decision a project
   makes deliberately in site.css, not something the template makes for it. */
a {
    color: var(--color-primary);
    text-decoration: underline;
    text-underline-offset: 0.2em;
    transition: color var(--duration-fast) var(--easing-standard);
}

a:hover {
    color: var(--color-primary-strong);
}

/* ---- focus --------------------------------------------------------------
   :focus-visible only, so a mouse click does not leave a ring behind, but
   every keyboard user gets one. Never removed without a replacement. */
:focus-visible {
    outline: 2px solid var(--color-focus);
    outline-offset: 2px;
    border-radius: var(--radius-sm);
}

/* The skip link is off-screen until it takes focus, which is the one case
   where a hidden element must become visible. */
.skip-link {
    position: absolute;
    top: var(--space-xs);
    left: var(--space-xs);
    z-index: var(--z-overlay);
    padding: var(--padding-xs) var(--padding-md);
    color: var(--color-text-invert);
    background-color: var(--color-primary);
    border-radius: var(--radius-md);
    text-decoration: none;
    transform: translateY(-200%);
}

.skip-link:focus-visible {
    transform: translateY(0);
}

/* ---- media -------------------------------------------------------------- */
img, picture, svg, video, canvas {
    display: block;
    max-width: 100%;
    height: auto;
}

svg { fill: currentColor; }

/* ---- forms and controls -------------------------------------------------- */
input, button, textarea, select {
    font: inherit;
    color: inherit;
}

button {
    cursor: pointer;
}

button:disabled,
input:disabled,
select:disabled,
textarea:disabled {
    color: var(--color-text-disabled);
    background-color: var(--color-bg-disabled);
    cursor: not-allowed;
}

input[type="text"],
input[type="search"],
input[type="email"],
input[type="url"],
input[type="tel"],
input[type="number"],
input[type="password"],
input[type="date"],
textarea,
select {
    width: 100%;
    padding: var(--padding-xs) var(--padding-sm);
    color: var(--color-text);
    background-color: var(--color-bg-card);
    border: var(--size-hairline) solid var(--color-line-strong);
    border-radius: var(--radius-md);
}

textarea {
    min-height: 8rem;
    resize: vertical;
}

label {
    display: inline-block;
    font-size: var(--fs-caption);
    font-weight: var(--fw-medium);
    color: var(--color-text-sub);
}

/* ---- one button, three intents ------------------------------------------ */
.button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-xs);
    min-height: 4.4rem;
    padding: var(--padding-xs) var(--padding-lg);
    font-size: var(--fs-body);
    font-weight: var(--fw-semibold);
    line-height: var(--lh-none);
    color: var(--color-text-invert);
    text-decoration: none;
    background-color: var(--color-primary);
    border: var(--size-hairline) solid transparent;
    border-radius: var(--radius-md);
    transition: background-color var(--duration-fast) var(--easing-standard);
}

.button:hover { background-color: var(--color-primary-strong); color: var(--color-text-invert); }
.button--quiet { color: var(--color-primary); background-color: transparent; border-color: var(--color-line-strong); }
.button--quiet:hover { color: var(--color-primary-strong); background-color: var(--color-primary-soft); }
.button--danger { background-color: var(--color-danger); }
.button--block { width: 100%; }

/* ---- surfaces ------------------------------------------------------------ */
.card {
    padding: var(--padding-lg);
    background-color: var(--color-bg-card);
    border: var(--size-hairline) solid var(--color-line);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-card);
}

.notice {
    padding: var(--padding-sm) var(--padding-md);
    font-size: var(--fs-caption);
    color: var(--color-text-sub);
    background-color: var(--color-bg-soft);
    border-left: 3px solid var(--color-line-strong);
    border-radius: var(--radius-sm);
}

.notice--danger {
    color: var(--color-danger);
    background-color: var(--color-danger-soft);
    border-left-color: var(--color-danger);
}

/* ---- accordion: mechanics only --------------------------------------------
   The open/close half of the shared disclosure component in ux.js. Everything
   here is the part that must not be re-invented per project: the trigger is a
   full-width button, the panel grows from a 0fr grid row so it animates to its
   own content height with no measured pixel value, and the chevron turns.

   Nothing here paints. Colours, radii, borders, shadows, padding, type and the
   chevron artwork belong to the project's own stylesheet, which loads after
   this file and wins on equal specificity. A project whose chevron icon points
   the other way simply restates the two transform rules.

   Markup contract: [data-accordion] > .accordion__item >
   .accordion__trigger[aria-controls] + .accordion__panel > .accordion__panel-inner
   ---------------------------------------------------------------------------- */
.accordion__trigger {
    display: flex;
    align-items: center;
    width: 100%;
    text-align: left;
    background: none;
    border: 0;
    cursor: pointer;
}

.accordion__chevron {
    flex: 0 0 auto;
    transition: transform var(--duration-base) var(--easing-standard);
}

/* visibility is switched at the far end of the transition when closing and at
   the near end when opening, so a closed panel is really hidden from focus and
   find-in-page rather than merely flat. */
.accordion__panel {
    display: grid;
    grid-template-rows: 0fr;
    visibility: hidden;
    opacity: 0;
    transition:
        grid-template-rows var(--duration-base) var(--easing-standard),
        opacity var(--duration-base) var(--easing-standard),
        visibility 0s linear var(--duration-base);
}

.accordion__panel-inner {
    min-height: 0;
    overflow: hidden;
}

.accordion__item.is-open .accordion__panel {
    grid-template-rows: 1fr;
    visibility: visible;
    opacity: 1;
    transition:
        grid-template-rows var(--duration-base) var(--easing-standard),
        opacity var(--duration-base) var(--easing-standard),
        visibility 0s linear 0s;
}

.accordion__item.is-open .accordion__chevron {
    transform: rotate(180deg);
}

/* ---- code and tables ------------------------------------------------------ */
code, kbd, samp, pre {
    font-family: var(--font-mono);
    font-size: 0.92em;
}

pre {
    padding: var(--padding-md);
    overflow-x: auto;
    background-color: var(--color-bg-soft);
    border-radius: var(--radius-md);
}

table {
    width: 100%;
    border-collapse: collapse;
}

th, td {
    padding: var(--padding-xs) var(--padding-sm);
    text-align: left;
    border-bottom: var(--size-hairline) solid var(--color-line);
}

th {
    font-weight: var(--fw-semibold);
    color: var(--color-text-sub);
}

/* ---- spinner: an indeterminate wait -------------------------------------
   Decorative by contract. It carries aria-hidden and says nothing to a
   screen reader, so pair it with real text whenever the wait needs
   announcing - the ring is the picture, the text is the message:

     <button class="button" aria-busy="true">
       <span class="spinner" aria-hidden="true"></span> 저장 중
     </button>

     <p role="status"><span class="spinner" aria-hidden="true"></span> 불러오는 중…</p>

   Sized in em from the current font and drawn in currentColor, so it fits
   inside a button or a line of text with no modifier and no colour of its
   own. Nothing here is full-page: a spinner that blocks the whole screen is
   a decision a project makes, not one this file makes for it. */
.spinner {
    display: inline-block;
    flex: 0 0 auto;
    width: 1em;
    height: 1em;
    vertical-align: -0.125em;
    border: 2px solid currentColor;
    border-right-color: transparent;
    border-radius: var(--radius-pill);
    animation: spinner-turn 640ms linear infinite;
}

.spinner--lg {
    width: 2.4rem;
    height: 2.4rem;
    border-width: 3px;
}

.spinner--center {
    display: block;
    margin: var(--space-lg) auto;
}

@keyframes spinner-turn {
    to { transform: rotate(360deg); }
}

/* ---- skeleton: the shape of content that has not arrived -----------------
   For a wait whose layout is predictable - a list, a card, a paragraph.

   Mark the whole group aria-hidden. A screen reader read three identical
   placeholder rows learns nothing and loses its place; announce the wait
   once, in text, outside the group:

     <div role="status" class="sr-only">불러오는 중…</div>
     <div class="skeleton" aria-hidden="true">
       <span class="skeleton__line skeleton__line--title"></span>
       <span class="skeleton__line"></span>
       <span class="skeleton__line skeleton__line--short"></span>
     </div>

   Heights are em, so a skeleton standing in for text matches the type size
   around it, and the colour is the existing line token rather than a grey
   invented here. */
.skeleton {
    display: grid;
    gap: var(--space-xs);
}

.skeleton__line,
.skeleton__block {
    display: block;
    background-color: var(--color-line);
    border-radius: var(--radius-sm);
    animation: skeleton-pulse 1400ms ease-in-out infinite;
}

.skeleton__line {
    height: 1em;
}

.skeleton__line--title {
    height: 1.4em;
}

.skeleton__line--short {
    width: 40%;
}

.skeleton__block {
    height: 12rem;
    border-radius: var(--radius-md);
}

@keyframes skeleton-pulse {
    50% { opacity: 0.45; }
}

/* ---- load state: one attribute picks which panel is showing --------------
   The five states an async region moves through, expressed as markup rather
   than as a class per state. AppLoading.set() writes the attribute; CSS does
   the rest, so a region works even before its script runs.

     <section data-load-state="loading">
       <div data-when="loading"><div class="skeleton" aria-hidden="true">…</div></div>
       <div data-when="success"><ul>…</ul></div>
       <div data-when="empty">결과가 없습니다.</div>
       <div data-when="error">불러오지 못했습니다.</div>
     </section>

   A panel may list more than one state: data-when="idle loading". */
[data-load-state] [data-when] {
    display: none;
}

[data-load-state="idle"] [data-when~="idle"],
[data-load-state="loading"] [data-when~="loading"],
[data-load-state="success"] [data-when~="success"],
[data-load-state="empty"] [data-when~="empty"],
[data-load-state="error"] [data-when~="error"] {
    /* revert, not block: a panel that is a flex or grid container keeps
       being one when it is the visible state. */
    display: revert;
}

/* ---- modal: overlay mechanics only ---------------------------------------
   Keyboard and focus behaviour lives in ux.js (AppModal); this file only
   places the overlay and the dialog box. Colours, radius and width above are
   token-driven, so a project restyles them in site.css without touching the
   open/close machinery.

     <button type="button" data-modal-open="example-modal">열기</button>

     <div id="example-modal" class="modal" data-modal role="dialog"
          aria-modal="true" aria-labelledby="example-modal-title" hidden>
       <div class="modal__dialog">
         <h2 id="example-modal-title">제목</h2>
         <p>…</p>
         <button type="button" class="button" data-modal-close>닫기</button>
       </div>
     </div> */
.modal {
    position: fixed;
    inset: 0;
    z-index: var(--z-overlay);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: calc(var(--padding-md) + var(--safe-top))
             calc(var(--padding-md) + var(--safe-right))
             calc(var(--padding-md) + var(--safe-bottom))
             calc(var(--padding-md) + var(--safe-left));
    background-color: var(--color-scrim);
}

/* display:flex above would outrank the browser's own [hidden] rule, which is
   the one thing that must not happen: AppModal opens and closes by toggling
   that attribute and nothing else. */
.modal[hidden] {
    display: none;
}

.modal__dialog {
    width: 100%;
    max-width: 48rem;
    max-height: 100%;
    overflow-y: auto;
    padding: var(--padding-lg);
    background-color: var(--color-bg-card);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-raised);
}

/* Set on <html> while a modal is open, so the page behind it does not
   scroll under the overlay. */
.u-modal-open {
    overflow: hidden;
}

/* ---- reduced motion -------------------------------------------------------
   Zeroing the duration tokens is enough for anything that uses them; the
   blanket rule catches animation a project adds later without asking. */
@media (prefers-reduced-motion: reduce) {
    :root {
        --duration-fast: 0s;
        --duration-base: 0s;
    }

    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }

    /* The blanket rule above stops the spinner mid-turn and freezes the
       skeleton at whatever opacity it had. Both are still meant to read as
       "waiting", so say what they look like when they cannot move: a static
       ring, and a flat block. */
    .spinner {
        border-right-color: currentColor;
        opacity: 0.45;
    }

    .skeleton__line,
    .skeleton__block {
        opacity: 1;
    }
}

/* 공용 UX — theme.ux 로 켠 화면에서만 관련 요소가 나온다. */
#loading-mask {
    position: fixed;
    inset: 0;
    background: #fff;
    z-index: 9999;
}

.u-anchor-pending {
    visibility: hidden;
}
