/* Project stylesheet.
   Written once. The generator never rewrites this file, so edit it freely.

   This file loads after tokens.css, utilities.css, common.css and layout.css.
   Do not constrain or pad body here: sections own their backgrounds at full
   viewport width, then put their readable content in a child .inner.

   <section class="sub-section">
       <div class="inner">...</div>
   </section>
*/

/* A harmless baseline for project-owned navigation. The shared header's
   class-based rules remain the stronger, component-specific layer. */
nav ul {
    display: flex;
    gap: var(--space-md);
    padding: 0;
    list-style: none;
}

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

/* ---- splash (entry screen at the site root) --------------------------------
   The entry shell public/index.php owns <main id="splash-entry"> and the
   redirect script; everything below styles only what src/App/Pages/splash.php
   puts inside it, so no other screen is touched.

   Figma frame 393 x 852. The brand lockup is one asset - Figma's "Frame 1",
   the mark above the wordmark, 118 x 193 - so it is sized by width alone and
   needs no webfont to render the wordmark correctly.

   The map artwork is exactly the bottom 265px band: splash-map-bg.png is that
   band at 2x (786 x 530). The scene therefore keeps a 393/265 ratio, sits on
   the bottom edge and scales with the width. The gradient over it is Figma's
   "Rectangle 1": page colour at the top fading out downwards, which melts the
   top edge of the artwork into the background instead of cutting it off. Like
   every fluid value here the scene stops growing at --inner-max-width, the
   width where the tokens pin.
   --------------------------------------------------------------------------- */
#splash-entry {
    background-color: var(--color-bg-soft);
}

#splash-stage {
    /* Figma lockup width 118px. The lockup is mostly the 땅땅 wordmark, and
       it sits right under the tagline, so it rides the font ramp (10/14
       minimum), not the spacing ramp. On the spacing ramp it would shrink to
       52% of the design at 393px while the tagline stayed at 79%, and the
       composition would come apart on the way down. */
    --splash-logo-width: clamp(84.2857px, calc(25.3446px + 23.5764vw), 118px);

    position: relative;
    display: flex;
    flex: 1 1 auto;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    padding-top: var(--safe-top);
    padding-bottom: var(--safe-bottom);
}

/* Above the artwork, and centred as a column: tagline, then the lockup. */
#splash-stage > .splash-stage__inner {
    position: relative;
    z-index: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-xl);
    text-align: center;
}

#splash-stage .splash-brand__tagline {
    font-family: var(--font-display);
    font-size: var(--fs-lead);
    font-weight: var(--fw-bold);
    line-height: var(--lh-none);
    color: var(--color-text-brand);
}

#splash-stage .splash-brand {
    display: flex;
    justify-content: center;
    line-height: 0;
}

#splash-stage .splash-brand__logo {
    width: var(--splash-logo-width);
    height: auto;
}

#splash-stage > .splash-stage__scene {
    position: absolute;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: 0;
    width: min(100%, var(--inner-max-width));
    margin-left: auto;
    margin-right: auto;
    aspect-ratio: 393 / 265;
    background-image:
        linear-gradient(to bottom, var(--color-bg-soft) 0%, transparent 100%),
        url("img/splash-map-bg.png");
    background-repeat: no-repeat;
    background-position: center bottom;
    background-size: 100% 100%;
    pointer-events: none;
}

/* ---- shared header, brand bar ----------------------------------------------
   The design gives the app one header: the navy brand bar with the white 땅땅
   lockup. --color-bg-brand is already documented in tokens.css as "header,
   active tab, button", so this is that token finally being used, not a new
   look invented here. Sub-pages switch to headerType "title_only", which
   layout.css styles separately and keeps its own height.
   --------------------------------------------------------------------------- */
.site-header {
    background-color: var(--color-bg-brand);
}

body.header-type-default .site-header > .inner {
    min-height: var(--header-home-height);
}

/* layout.css makes the logo link inline-flex, so it sits on a text baseline
   and the descender space below it pushes the mark a few px above the centre
   of the bar. Making the wrapper a flex container blockifies the link and
   removes that line box, which is what actually centres the logo. */
.site-header__logo {
    display: flex;
    align-items: center;
}

/* Figma "Frame 4": the mark and the 땅땅 wordmark are one 80 x 29 asset, so
   only the height is pinned. The header itself is a fixed-height band, which
   is why this is a flat rem and not a fluid clamp. */
.site-header__logo-icon {
    width: auto;
    height: 2.9rem;
}

/* ---- home (map) -------------------------------------------------------------
   The screen is the map: it fills everything under the header, and the search
   field, the two map controls and the tab bar float over it. So #home-wrap
   drops the .wrap padding and becomes the flex column that lets the map take
   the remaining height.

   The generated page-title band is kept in the DOM for the accessible <h1>
   and hidden visually - the design has no title bar.
   --------------------------------------------------------------------------- */
#home-wrap {
    display: flex;
    flex-direction: column;
    min-height: 0;
    padding-top: 0;
    padding-bottom: 0;
}

#home-wrap > .page-title {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

#home-wrap .home-map {
    position: relative;
    flex: 1 1 auto;
    min-height: 0;
    overflow: hidden;
}

/* OpenLayers mounts here and fills it. The background colour is what shows
   while tiles load, and behind the no-key notice. */
#home-wrap .home-map__canvas {
    position: absolute;
    inset: 0;
    background-color: var(--color-bg-soft);
}

#home-wrap .home-map__canvas-note {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 100%;
    padding: 0 var(--padding-md);
    font-size: var(--fs-caption);
    line-height: var(--lh-body);
    color: var(--color-text-placeholder);
    text-align: center;
    transform: translate(-50%, -50%);
}

/* The design draws its own controls, so the only OpenLayers chrome left is the
   attribution VWorld's terms require. It is parked above the native bottom
   navigation, in the room --footer-height reserves. */
#home-wrap .ol-attribution {
    right: calc(var(--padding-md) + var(--safe-right));
    bottom: calc(var(--footer-height) + var(--safe-bottom));
    left: auto;
    max-width: 100%;
    padding: 0.2rem 0.6rem;
    font-size: var(--fs-caption);
    background-color: var(--color-bg-glass);
    border-radius: var(--radius-sm);
}

#home-wrap .ol-attribution ul {
    margin: 0;
    padding: 0;
    color: var(--color-text-sub);
    text-shadow: none;
}

#home-wrap .ol-attribution button {
    display: none;
}

/* Everything that floats over the map. The wrapper itself must not eat taps
   meant for the map, so it is transparent to pointers and each control turns
   them back on. */
#home-wrap .home-map__overlay {
    position: absolute;
    top: 0;
    right: 0;
    left: 0;
    z-index: var(--z-float);
    padding-top: var(--padding-md);
    pointer-events: none;
}

#home-wrap .home-map__overlay-inner {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    /* Figma leaves 21px between the search field and the first map control
       (16px of search-bar padding plus a 5px offset); 20 is that on the
       spacing ladder. */
    gap: var(--space-20);
}

/* ---- search field (home overlay and the search screen) ---------------------- */
.app-search {
    display: flex;
    align-items: center;
    gap: var(--space-xs);
    width: 100%;
    min-height: 4rem;
    padding: var(--padding-xs);
    background-color: var(--color-bg);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-card);
}

.app-search__icon {
    flex: 0 0 auto;
    width: var(--icon-size);
    height: var(--icon-size);
}

/* The magnifier is a submit button on the search screen. It carries no box of
   its own - the icon is the whole control - so everything a button brings by
   default is taken back off. */
.app-search__submit {
    display: flex;
    flex: 0 0 auto;
    align-items: center;
    min-height: 0;
    padding: 0;
    background: none;
    border: 0;
    cursor: pointer;
}

/* On the map screen the bar is a link to the search screen, not a field. It has
   to read as the same component, so the placeholder text is styled like the
   real placeholder it stands in for. */
.app-search--link {
    text-decoration: none;
}

.app-search__placeholder {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    font-size: var(--fs-body);
    font-weight: var(--fw-medium);
    line-height: var(--lh-body);
    color: var(--color-text-placeholder);
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* A search that was actually made, rather than the invitation to make one. */
.app-search__placeholder--term {
    color: var(--color-text);
}

/* The bar is two controls side by side: tapping the text returns to the result
   list, tapping the x drops the search. So the link is a child now, not the bar
   itself. */
.app-search__target {
    display: flex;
    flex: 1 1 auto;
    gap: var(--space-xs);
    align-items: center;
    min-width: 0;
    text-decoration: none;
}

.app-search__clear {
    display: inline-flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: center;
    width: var(--icon-size);
    height: var(--icon-size);
    padding: 0;
    color: var(--color-text-sub);
    background: none;
    border: 0;
    cursor: pointer;
}

/* common.css styles text inputs through input[type="search"], which outranks a
   bare class, so the field is reached through its wrapper to win on weight. */
.app-search .app-search__input {
    flex: 1 1 auto;
    min-width: 0;
    padding: 0;
    font-family: var(--font-base);
    font-size: var(--fs-body);
    font-weight: var(--fw-medium);
    line-height: var(--lh-body);
    color: var(--color-text);
    background: none;
    border: 0;
}

.app-search .app-search__input::placeholder {
    color: var(--color-text-placeholder);
}

.app-search .app-search__input::-webkit-search-cancel-button {
    -webkit-appearance: none;
    appearance: none;
}

/* On the map the field floats over the tiles, so it has to take taps back
   from the overlay wrapper, which is transparent to them. */
#home-wrap .app-search {
    pointer-events: auto;
}

/* ---- floating map controls -------------------------------------------------- */
#home-wrap .home-map__controls {
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
    pointer-events: auto;
}

#home-wrap .home-map__control {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--map-control-size);
    height: var(--map-control-size);
    padding: 0;
    color: var(--color-icon);
    background-color: var(--color-bg);
    border: 0;
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-raised);
    cursor: pointer;
}

/* Figma puts a 30px icon inside the 40px control, not a 24px one. */
/* Sized like the icon it stands in for, so the button does not change shape. */
#home-wrap .home-map__control-spinner {
    width: 2rem;
    height: 2rem;
    border-width: 3px;
    color: var(--color-text-brand);
}

#home-wrap .home-map__control-icon[hidden] {
    display: none;
}

#home-wrap .home-map__control-icon {
    width: 3rem;
    height: 3rem;
}

/* ---- 지도 설정 modal ----------------------------------------------------------
   Open/close, focus trap and Escape come from the shared .modal component
   (common.css + AppModal in ux.js); only the look is set here.

   Figma: 336.5px wide, 12px header padding, 24px body padding, three 85.5px
   cards with a 16px gap. Selection is three real radios, visually hidden, so
   arrow keys work and the highlight needs no script.
   ------------------------------------------------------------------------------ */
#map-settings .map-settings__dialog {
    max-width: 33.65rem;
    /* The header and body own their padding, and the card row must be able
       to reach the rounded corners cleanly. */
    padding: 0;
    overflow: hidden;
}

#map-settings .map-settings__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-sm);
    padding: var(--padding-sm);
}

/* Balances the close button so the title sits optically centred. */
#map-settings .map-settings__head-spacer {
    flex: 0 0 auto;
    width: var(--icon-size);
    height: var(--icon-size);
}

/* common.css sets every h1-h6 in the display face; this title is body face. */
#map-settings .map-settings__title {
    margin: 0;
    font-family: var(--font-base);
    font-size: var(--fs-body);
    font-weight: var(--fw-medium);
    line-height: var(--lh-body);
    letter-spacing: var(--ls-tight);
    color: var(--color-text);
}

#map-settings .map-settings__close {
    display: inline-flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: center;
    width: var(--icon-size);
    height: var(--icon-size);
    padding: 0;
    color: var(--color-text-mute);
    background: none;
    border: 0;
    cursor: pointer;
}

#map-settings .map-settings__body {
    padding: var(--padding-lg);
}

#map-settings .map-settings__fieldset {
    min-width: 0;
    margin: 0;
    padding: 0;
    border: 0;
}

#map-settings .map-settings__types {
    display: flex;
    gap: var(--space-md);
}

/* The radios are .sr-only, so they are out of flow and only the cards lay out. */
#map-settings .map-settings__card {
    display: flex;
    flex: 1 1 0;
    flex-direction: column;
    min-width: 0;
    overflow: hidden;
    cursor: pointer;
    background-color: var(--color-bg-soft);
    border: var(--size-hairline) solid var(--color-line-strong);
    border-radius: var(--radius-md);
}

/* Figma: a 64.12px picture sitting on the bottom of an 83.5 x 78.38 area,
   kept as ratios so the card can flex with the dialog. */
#map-settings .map-settings__figure {
    display: flex;
    align-items: flex-end;
    justify-content: center;
    aspect-ratio: 83.5 / 78.38;
}

#map-settings .map-settings__figure img {
    width: 76.8%;
    height: auto;
    border-radius: var(--radius-sm);
}

/* Figma's 2.85 / 4.275px label padding is far below the 10px padding-token
   floor and would triple the row height, so the one-off value is used here. */
#map-settings .map-settings__label {
    padding: 0.285rem 0.4275rem;
    font-size: var(--fs-label);
    font-weight: var(--fw-medium);
    line-height: var(--lh-body);
    letter-spacing: var(--ls-tight);
    color: var(--color-text-sub);
    text-align: center;
}

#map-settings .map-settings__input:checked + .map-settings__card {
    background-color: var(--color-bg-brand);
    border-color: var(--color-bg-brand);
}

#map-settings .map-settings__input:checked + .map-settings__card .map-settings__label {
    color: var(--color-text-invert);
}

#map-settings .map-settings__input:focus-visible + .map-settings__card {
    outline: var(--size-stroke) solid var(--color-focus);
    outline-offset: var(--size-stroke);
}

/* ---- search screen ------------------------------------------------------------
   Figma "Search_장소검색". The navy runs from the status bar through the nav bar
   and the search bar in one block, so the title_only header drops its hairline
   and the search band carries the same brand colour underneath it.
   ------------------------------------------------------------------------------ */
body.header-type-title_only .site-header {
    border-bottom: 0;
}

/* Figma's sub-page nav bar is 46px, which is what --header-height already is;
   layout.css ships a taller default. */
body.header-type-title_only .site-header > .inner {
    min-height: var(--header-height);
    padding-top: 0;
    padding-bottom: 0;
}

/* The shared header draws its own chevron as inline SVG. This screen has a
   supplied asset for it, so the SVG is swapped for that image in place. */
body.header-type-title_only .site-header__back {
    background-image: url("img/icon-chevron-left-white.png");
    background-repeat: no-repeat;
    background-position: center;
    background-size: var(--icon-size) var(--icon-size);
}

body.header-type-title_only .site-header__back svg {
    display: none;
}

body.header-type-title_only .site-header__page-title {
    font-weight: var(--fw-semibold);
    line-height: var(--lh-heading);
    color: var(--color-text-invert);
}

#search-wrap,
#glossary-wrap,
#saved-wrap {
    /* A column, so the empty and no-result states can centre themselves in
       whatever height is left under the search bar. */
    display: flex;
    flex-direction: column;
    padding-top: 0;
    /* Clear the native bottom navigation so the last result is never under it. */
    padding-bottom: calc(var(--footer-height) + var(--safe-bottom));
}

#search-wrap > .page-title,
#glossary-wrap > .page-title,
#saved-wrap > .page-title {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* The navy band the search field sits in. Shared by 검색 and 용어사전, which
   both continue the header's colour down past the field. */
.app-search-bar {
    padding-top: var(--padding-md);
    padding-bottom: var(--padding-md);
    background-color: var(--color-bg-brand);
}

/* ---- empty and no-result states ------------------------------------------------
   Shared by 검색 and 용어사전: an 85px mark over one centred line of guidance,
   vertically centred in whatever height is left under the search bar.

   85px is the Figma frame. The artwork inside each asset is inset - the book
   mark draws 75 x 58 of its 85 x 85 box - so sizing the frame, not the visible
   art, is what makes the marks agree across screens.
   --------------------------------------------------------------------------------- */
.app-empty {
    display: flex;
    flex: 1 1 auto;
    align-items: center;
    justify-content: center;
}

/* [hidden] means display:none only until a class sets a display of its own, and
   these four all do. 저장 switches between them from JavaScript, so each has to
   give the attribute its meaning back. */
.app-empty[hidden],
.app-count[hidden],
.saved-list[hidden],
.app-pagination[hidden] {
    display: none;
}

.app-empty__inner {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-20);
    text-align: center;
}

.app-empty__icon {
    width: 8.5rem;
    height: auto;
}

.app-empty__text {
    font-size: var(--fs-body);
    font-weight: var(--fw-medium);
    line-height: var(--lh-body);
    color: var(--color-text-sub);
}

/* The retry the failed-search state offers. Shaped like the 정부24 hand-off on
   등본발급, which is the same button in the same designs; .button carries the
   colour and .button--block the width, so only the size is set here. */
/* .spinner is inline-block, so the attribute needs saying out loud. Inside a
   button, .button's own flex row places it beside the label. */
.spinner[hidden] {
    display: none;
}

#search-wrap .search-retry {
    padding: var(--padding-sm) var(--padding-lg);
    font-size: var(--fs-title);
    line-height: var(--lh-heading);
    border-radius: var(--radius-lg);
}

/* ---- result count strip -------------------------------------------------------- */
#search-wrap .search-count > .inner {
    padding-top: var(--padding-sm);
    padding-bottom: var(--padding-sm);
    border-bottom: var(--size-hairline) solid var(--color-line);
}

#search-wrap .search-count__text {
    font-size: var(--fs-label);
    font-weight: var(--fw-medium);
    line-height: var(--lh-body);
    color: var(--color-text-sub);
}

/* ---- result list ---------------------------------------------------------------
   Each row is a full-bleed band with its own hairline, and the reading width
   comes from the .inner inside the button rather than from the band.
   ------------------------------------------------------------------------------ */
#search-wrap .search-results__list {
    margin: 0;
    padding: 0;
    list-style: none;
}

/* The row is a link to the map screen, so common.css's underline and link
   colour are taken back off - the whole row is the target, and it reads as a
   list item rather than as a run of linked text. */
#search-wrap .search-result {
    display: block;
    width: 100%;
    padding: 0;
    font: inherit;
    color: inherit;
    text-align: left;
    text-decoration: none;
    background: none;
    border: 0;
    cursor: pointer;
}

/* The divider belongs to .inner, not to the row: the row is full-bleed, so a
   border on it ran the whole window - 1265px of hairline under 760px of text.
   On .inner it stops where the content stops, at any width. */
#search-wrap .search-result__inner {
    border-bottom: var(--size-hairline) solid var(--color-line);
}

#search-wrap .search-result:hover {
    color: inherit;
}

/* A row VWorld gave no coordinates for: still readable, but not a target. */
#search-wrap .search-result--flat {
    cursor: default;
}

#search-wrap .search-result__inner {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    padding-top: var(--padding-md);
    padding-bottom: var(--padding-md);
}

#search-wrap .search-result__body {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-4);
    min-width: 0;
}

#search-wrap .search-result__badge {
    padding: 0.2rem var(--padding-xs);
    font-size: var(--fs-label);
    font-weight: var(--fw-bold);
    line-height: var(--lh-body);
    color: var(--color-text-invert);
    background-color: var(--color-bg-brand);
    border-radius: var(--radius-xs);
}

#search-wrap .search-result__name {
    font-size: var(--fs-title);
    font-weight: var(--fw-bold);
    line-height: var(--lh-body);
    color: var(--color-text);
    word-break: keep-all;
}

#search-wrap .search-result__address {
    font-size: var(--fs-body);
    font-weight: var(--fw-regular);
    line-height: var(--lh-body);
    color: var(--color-text);
}

#search-wrap .search-result__chevron {
    display: block;
    flex: 0 0 auto;
    align-self: center;
    width: var(--icon-size);
    height: var(--icon-size);
}

/* Saved: the outline fills in. Same mark either way, so the button does not
   jump when it is pressed. */
.land-sheet .land-card__bookmark[aria-pressed="true"] svg path {
    fill: currentColor;
}

/* Saving and un-saving the same parcel twice in a row looks like nothing
   happened if the only difference is a fill. The mark gives a beat each time it
   is pressed, so a repeat reads as a repeat. */
@keyframes bookmark-beat {
    0% { transform: scale(1); }
    40% { transform: scale(1.28); }
    100% { transform: scale(1); }
}

.land-sheet .land-card__bookmark[data-beat] svg {
    animation: bookmark-beat var(--duration-base) var(--easing-standard);
}

@media (prefers-reduced-motion: reduce) {
    .app-toast {
        transition: none;
    }

    .land-sheet .land-card__bookmark[data-beat] svg {
        animation: none;
    }
}

/* ---- toast ---------------------------------------------------------------------
   Figma "북마크 완료얄럿": 299x64, 20px/16px padding, a 24px mark beside 14px
   text, both in --color-success on --color-success-soft. Those two tokens were
   already in tokens.css, labelled for exactly this.

   Fixed above the tab bar and over everything, including the sheet, because a
   confirmation that the sheet covers has confirmed nothing.
   ------------------------------------------------------------------------------ */
.app-toast {
    position: fixed;
    /* Clear of the native bottom bar, which is drawn over the WebView rather
       than reserved inside it: at 4.8rem the toast sat behind that bar, so
       the confirmation it carries never arrived. --space-20 is the gap that
       lifts it off the bar without climbing into what it is confirming. The
       home indicator sits under the bar, so --footer-height covers it. */
    bottom: calc(var(--footer-height) + var(--safe-bottom) + var(--space-20));
    left: 50%;
    z-index: var(--z-toast);
    display: flex;
    align-items: center;
    gap: var(--space-10);
    width: 29.9rem;
    max-width: calc(100% - 3.2rem);
    padding: var(--padding-20) var(--padding-md);
    background-color: var(--color-success-soft);
    border-radius: var(--radius-md);
    box-shadow: 0 0 1rem rgba(0, 0, 0, 0.5);
    /* It rises into place. hidden still does the hiding - it is what keeps the
       message out of the accessibility tree between appearances - so the fade
       is driven by data-shown, which toast.js sets one frame later. */
    opacity: 0;
    transition: opacity var(--duration-base) var(--easing-standard),
                transform var(--duration-base) var(--easing-standard);
    transform: translateX(-50%) translateY(0.8rem);
}

.app-toast[data-shown] {
    opacity: 1;
    transform: translateX(-50%) translateY(0);
}

.app-toast[hidden] {
    display: none;
}

.app-toast__icon {
    flex: 0 0 auto;
    width: var(--icon-size);
    height: var(--icon-size);
}

.app-toast__icon[hidden] {
    display: none;
}

.app-toast__text {
    margin: 0;
    font-size: var(--fs-label);
    font-weight: var(--fw-medium);
    line-height: var(--lh-body);
    color: var(--color-success);
}

/* ---- alert dialog -------------------------------------------------------------
   Figma "alert / Modal dialogs": 343px wide, a warning mark over centred text,
   and a row of two equal actions divided by hairlines. Reusable - the location
   permission message is just its first caller.

   The tokens were already cut for it: --shadow-dialog and --radius-sm are this
   dialog's shadow and corner, and --color-warning is its mark.

   Only the look lives here. Opening, the focus trap, Escape and the scroll lock
   are the shared .modal component (common.css + AppModal in ux.js).
   ------------------------------------------------------------------------------ */
.app-alert .app-alert__dialog {
    display: flex;
    flex-direction: column;
    gap: var(--space-xl);
    max-width: 34.3rem;
    /* The action row has to reach the dialog's edges, so the top padding is
       the dialog's and the side padding belongs to the content block. */
    padding: var(--padding-xl) 0 0;
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-dialog);
}

.app-alert .app-alert__content {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-md);
    padding: 0 var(--padding-xl);
}

.app-alert .app-alert__icon {
    flex: 0 0 auto;
    width: 3.6rem;
    height: 3.6rem;
}

.app-alert .app-alert__text {
    display: flex;
    flex-direction: column;
    gap: var(--space-md);
    width: 100%;
    text-align: center;
}

/* common.css sets every heading in the display face; this one is body face. */
.app-alert .app-alert__title {
    margin: 0;
    font-family: var(--font-base);
    font-size: var(--fs-body);
    font-weight: var(--fw-regular);
    line-height: var(--lh-tight);
    letter-spacing: var(--ls-normal);
    color: var(--color-text);
}

.app-alert .app-alert__body {
    font-size: var(--fs-label);
    font-weight: var(--fw-regular);
    line-height: var(--lh-tight);
    color: var(--color-text);
}

/* Two equal actions. The divider is one border between them, under a single
   rule that runs the width of the dialog. */
.app-alert .app-alert__actions {
    display: flex;
    border-top: var(--size-hairline) solid var(--color-line);
}

.app-alert .app-alert__action {
    flex: 1 1 0;
    min-width: 0;
    min-height: 5.5rem;
    padding: var(--padding-xs);
    font-family: var(--font-base);
    font-size: var(--fs-label);
    font-weight: var(--fw-regular);
    line-height: var(--lh-tight);
    color: var(--color-text);
    background: none;
    border: 0;
    cursor: pointer;
}

.app-alert .app-alert__action + .app-alert__action {
    border-left: var(--size-hairline) solid var(--color-line);
}

/* ---- 토지정보 바텀시트 ------------------------------------------------------------
   Figma "바텀시트_토지정보". Built on the shared .modal so the focus trap,
   Escape and the scroll lock come for free, then re-anchored: the panel sits
   on the bottom edge, full width, with only its top corners rounded.

   No scrim. The sheet is about the parcel under it, so dimming the map would
   hide the thing being described; the design frame shows no scrim either.

   The tokens are the ones tokens.css already reserved for this screen:
   --radius-sheet, --shadow-sheet, --color-bg-table-head, --color-bg-accent,
   --color-bg-disabled (the handle) and --color-scrollbar.
   ------------------------------------------------------------------------------ */
.land-sheet {
    align-items: flex-end;
    /* .modal stacks at --z-overlay, which is for dialogs with a scrim over the
       whole screen. This is a panel: it belongs under the toast that confirms
       what was done in it. */
    z-index: var(--z-sheet);
    padding: 0;
    /* The bottom navigation is native Android drawn over the WebView, not
       markup here, so the sheet has to stop above it - otherwise its lower
       part, and while peeking most of it, sits behind that bar. Named once
       because the panel's two heights below subtract it to keep their own
       top edge where it was. --safe-bottom rides along as the token asks,
       and the home indicator is below the native bar either way. */
    --sheet-bottom-inset: calc(var(--footer-height) + var(--safe-bottom));
    padding-bottom: var(--sheet-bottom-inset);
    /* Where the sheet stops when it is pulled all the way up: just under the
       search field. Built from the things above it rather than a measured
       number, so it follows the header and the field if either changes -
       the home bar, the overlay's own top padding, the field itself (its
       24px mark plus 10px of padding each side, which beats its 4rem
       min-height), and the gap the design already puts under the field.
       That lands at 134px at 393px and wider, where the fluid tokens stop
       growing, and less on narrower screens. */
    --sheet-top-clearance: calc(var(--header-home-height) + var(--padding-md)
        + var(--icon-size) + var(--padding-xs) + var(--padding-xs) + var(--space-20));
    background-color: transparent;
    /* The map has to stay reachable around the sheet. */
    pointer-events: none;
}

/* The strip the sheet leaves under itself is where the native bottom bar is
   drawn. Left transparent, the map showed through it and the sheet read as
   floating above a seam. Filling it with the panel's own background carries
   the sheet down to the bottom edge instead, so the bar sits on white rather
   than on the map.

   It comes and goes with the sheet, because .land-sheet is the element
   [hidden] toggles, and it takes no taps - pointer-events: none above
   covers this pseudo-element too. It cannot overlap the panel either: the
   panel is a flex item resting at the end of the content box, and this band
   is exactly the padding below it.

   Centred on the same column as the panel so a window wider than
   --inner-max-width does not get a white band wider than the sheet above
   it. left + translateX rather than auto margins, which an absolutely
   positioned box with both edges pinned resolves to left-aligned. */
.land-sheet::after {
    content: "";
    position: absolute;
    bottom: 0;
    left: 50%;
    width: 100%;
    max-width: var(--inner-max-width);
    height: var(--sheet-bottom-inset);
    background-color: var(--color-bg);
    transform: translateX(-50%);
}

.land-sheet .land-sheet__panel {
    display: flex;
    flex-direction: column;
    width: 100%;
    /* .modal__dialog caps at 48rem, which is narrower than this screen's content
       column; none at all let the sheet run the whole window on a wide one while
       everything else stopped at --inner-max-width. It is the same column. */
    max-width: var(--inner-max-width);
    /* Pulled all the way up the sheet now reaches to just under the search
       field - see --sheet-top-clearance. That is close to what Figma draws
       (712 of an 852 frame, a 140px top), and it does cover the GPS and
       layer buttons, which sit from 135px down. Reaching them means easing
       the sheet back down, which the grip and a pull on the content both
       do. dvh rather than vh so a mobile toolbar cannot clip the sheet. */
    max-height: calc(100dvh - var(--sheet-top-clearance) - var(--sheet-bottom-inset));
    /* The drag sets an inline height; these two are where it settles. */
    transition: height var(--duration-base) var(--easing-standard);
    padding: 0;
    overflow: hidden;
    pointer-events: auto;
    background-color: var(--color-bg);
    border-radius: var(--radius-sheet) var(--radius-sheet) 0 0;
    box-shadow: none;
    /* Figma applies this as a drop-shadow filter, which follows the rounded
       top edge instead of the box. */
    filter: drop-shadow(var(--shadow-sheet));
}

/* Grab handle. */
.land-sheet .land-sheet__handle {
    display: flex;
    flex: 0 0 auto;
    justify-content: center;
    padding: var(--padding-xs) 0;
}

.land-sheet .land-sheet__handle span {
    width: 8rem;
    height: 0.5rem;
    background-color: var(--color-bg-disabled);
    border-radius: var(--radius-pill);
}

.land-sheet .land-sheet__head {
    display: flex;
    flex: 0 0 auto;
    justify-content: flex-end;
    padding: var(--padding-sm);
}

.land-sheet .land-sheet__close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--icon-size);
    height: var(--icon-size);
    padding: 0;
    color: var(--color-text-mute);
    background: none;
    border: 0;
    cursor: pointer;
}

/* The only scrolling region. Figma draws its own slim thumb on the right. */
.land-sheet .land-sheet__body {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    gap: var(--space-md);
    min-height: 0;
    overflow-y: auto;
    /* No --safe-bottom in the bottom padding: --sheet-bottom-inset already
       lifts the whole panel clear of the native bar and of the home
       indicator beneath it, so paying it again here only left dead space
       at the end of the scroll. */
    padding: var(--padding-sm) var(--padding-sm) var(--padding-xl);
    scrollbar-width: thin;
    scrollbar-color: var(--color-scrollbar) transparent;
}

/* The failure view: same panel, same close button, different contents. It is
   centred in whatever height the sheet has rather than scrolling, because there
   is only ever this much of it. */
.land-sheet .land-sheet__fail {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-20);
    min-height: 0;
    padding: var(--padding-sm) var(--padding-sm) var(--padding-xl);
    text-align: center;
}

/* [hidden] is display:none only until a class says otherwise, and both views
   here are flex containers, so each has to give the attribute its meaning back. */
.land-sheet .land-sheet__body[hidden],
.land-sheet .land-sheet__fail[hidden] {
    display: none;
}

/* The waiting view shares the failure view's centred box - one line instead of
   three - and takes its height from the sheet's own state, so it appears inside
   the peeking sheet without pushing it open. */
.land-sheet .land-sheet__loading .app-empty__text {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-xs);
}

/* The two resting heights.

   Peeking is 130px, which is the grip, the close button, the top of the 소재지
   card and its heading - measured: the heading sits 98-122px down the panel and
   the address begins at 138px, so this cuts cleanly between them. Enough to say
   "there is something here" without taking the map away. */
.land-sheet[data-sheet="peek"] .land-sheet__panel {
    height: 13rem;
}

.land-sheet[data-sheet="full"] .land-sheet__panel {
    height: calc(100dvh - var(--sheet-top-clearance) - var(--sheet-bottom-inset));
}

/* Nothing to scroll while peeking, and the whole panel is a drag handle. */
.land-sheet[data-sheet="peek"] .land-sheet__body {
    overflow: hidden;
}

/* touch-action keeps a finger's drag away from the browser; user-select is the
   same job for a mouse. Pressing and moving on a surface the browser thinks is
   text starts a selection, and starting one makes it take the gesture back with
   a pointercancel - the drag began and died in the same frame, which read as
   "the sheet will not come up when I grab the top".

   Only the drag surfaces. The sheet's body stays selectable in the full state
   so an address can still be copied. */
.land-sheet[data-sheet="peek"] .land-sheet__panel,
.land-sheet .land-sheet__handle,
.land-sheet .land-sheet__head {
    touch-action: none;
    -webkit-user-select: none;
    user-select: none;
}

/* While a finger is on it the height follows the finger, so no easing. */
.land-sheet[data-dragging] .land-sheet__panel {
    transition: none;
}

/* Shaped like the 다시 시도 on the search screen, which is the same button in
   the same designs. */
.land-sheet .land-sheet__retry {
    align-self: stretch;
    padding: var(--padding-sm) var(--padding-lg);
    font-size: var(--fs-title);
    line-height: var(--lh-heading);
    border-radius: var(--radius-lg);
}

.land-sheet .land-sheet__body::-webkit-scrollbar {
    width: 0.6rem;
}

.land-sheet .land-sheet__body::-webkit-scrollbar-thumb {
    background-color: var(--color-scrollbar);
    border-radius: var(--radius-pill);
}

/* ---- 소재지 card ---------------------------------------------------------------- */
.land-sheet .land-card {
    display: flex;
    flex-direction: column;
    gap: var(--space-md);
    padding: var(--padding-sm);
    border: var(--size-hairline) solid var(--color-line-strong);
    border-radius: var(--radius-md);
}

/* The spacer opposite the bookmark keeps the title optically centred. */
.land-sheet .land-card__head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-md);
}

.land-sheet .land-card__bookmark {
    display: inline-flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: center;
    width: var(--icon-size);
    height: var(--icon-size);
    padding: 0;
    color: var(--color-text-mute);
    background: none;
    border: 0;
    cursor: pointer;
}

.land-sheet .land-card__spacer {
    flex: 0 0 auto;
    width: var(--icon-size);
    height: var(--icon-size);
}

.land-sheet .land-card__title {
    margin: 0;
    font-family: var(--font-base);
    font-size: var(--fs-body);
    font-weight: var(--fw-semibold);
    line-height: var(--lh-body);
    letter-spacing: var(--ls-tight);
    color: var(--color-text);
}

.land-sheet .land-card__value {
    font-size: var(--fs-body);
    font-weight: var(--fw-medium);
    line-height: var(--lh-body);
    letter-spacing: var(--ls-tight);
    color: var(--color-text-brand);
    text-align: center;
}

/* ---- the three fact tables -------------------------------------------------------
   Three tables rather than one, because Figma separates them by a 12px gap and
   each is its own set of column headers over its own values.
   ---------------------------------------------------------------------------------- */
.land-sheet .land-tables {
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
}

.land-sheet .land-table {
    width: 100%;
    table-layout: fixed;
    border-collapse: collapse;
}

/* Figma's cell padding is 8px 6px, written out rather than taken from
   --padding-6: that token's 10px floor is there for touch targets, and a table
   cell is not one.
   The header row gets 4px instead. The longest label, "개별공시지가(원/㎡)",
   measures 114.8px in the web Pretendard where Figma's own metrics put it at
   109px; at 6px padding it folds onto a second line and the header row grows
   from 45px to 58px. 4px leaves 115px and keeps it on one line. If a font
   fallback ever pushes it over, the row simply gets taller - nothing clips. */
.land-sheet .land-table th,
.land-sheet .land-table td {
    padding: var(--padding-xs) 0.6rem;
    font-size: var(--fs-label);
    line-height: var(--lh-body);
    letter-spacing: var(--ls-tight);
    text-align: center;
    word-break: keep-all;
}

/* The white hairline under the header row is Figma's border-bottom, which
   reads as a gap between the tinted header and the value. */
.land-sheet .land-table th {
    padding-right: 0.4rem;
    padding-left: 0.4rem;
    font-weight: var(--fw-semibold);
    color: var(--color-text);
    background-color: var(--color-bg-table-head);
    border-bottom: var(--size-hairline) solid var(--color-bg);
}

/* common.css rules every td with a hairline under it, which is right for a data
   table on a page and wrong here: this sheet draws its rows as tinted header
   over plain value, with no rule between rows. */
.land-sheet .land-table td {
    font-weight: var(--fw-medium);
    color: var(--color-text-brand);
    border-bottom: 0;
}

.land-sheet .land-sheet__source {
    font-size: var(--fs-caption);
    font-weight: var(--fw-light);
    line-height: var(--lh-body);
    letter-spacing: var(--ls-tight);
    color: var(--color-text-sub);
    text-align: right;
}

/* ---- 확인해 주세요 ----------------------------------------------------------------- */
.land-sheet .land-notice {
    display: flex;
    flex-direction: column;
    gap: var(--space-xs);
    padding: var(--padding-md) var(--padding-sm);
    background-color: var(--color-bg-accent);
    border-radius: var(--radius-sm);
}

.land-sheet .land-notice__title {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    font-size: var(--fs-label);
    font-weight: var(--fw-bold);
    line-height: var(--lh-snug);
    letter-spacing: var(--ls-tight);
    color: var(--color-text-brand);
}

.land-sheet .land-notice__title svg {
    flex: 0 0 auto;
}

.land-sheet .land-notice__list {
    display: flex;
    flex-direction: column;
    gap: var(--space-xs);
    margin: 0;
    padding: 0;
    list-style: none;
}

/* 씨:리얼 is the one link in the notice. It sits inside a sentence, so it keeps
   the sentence's colour - they are the same navy anyway - and is marked by
   weight alone rather than by the underline common.css gives every link. */
.land-sheet .land-notice__link {
    font-weight: var(--fw-bold);
    color: inherit;
    text-decoration: none;
}

/* Figma's 3px dot, set on the first line rather than centred on the block. */
.land-sheet .land-notice__list li {
    position: relative;
    padding-left: var(--space-xs);
    font-size: var(--fs-label);
    font-weight: var(--fw-regular);
    line-height: var(--lh-body);
    letter-spacing: var(--ls-tight);
    color: var(--color-text-brand);
    word-break: keep-all;
}

.land-sheet .land-notice__list li::before {
    position: absolute;
    top: calc(var(--fs-label) * var(--lh-body) / 2 - 0.15rem);
    left: 0;
    width: 0.3rem;
    height: 0.3rem;
    content: "";
    background-color: var(--color-text-brand);
    border-radius: 50%;
}

/* ---- 등본발급 안내 --------------------------------------------------------------
   Figma "Issued Guide_기본". A guide screen under the same title_only header as
   검색: banner, two explained sections, then the hand-off to 정부24.

   --color-line-brand is the token tokens.css reserved for "section heading
   rule", which is exactly the line trailing each heading here.
   ------------------------------------------------------------------------------ */
#certificate-wrap {
    padding-top: 0;
    /* Clear the native bottom navigation. */
    padding-bottom: calc(var(--footer-height) + var(--safe-bottom));
}

#certificate-wrap > .page-title {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* Banner: 393 x 200 in Figma, drawn edge to edge. It sits in .inner so a wide
   window stops it at --inner-max-width with the rest of the page rather than
   letting it run to the edge of the browser on its own - but the side padding
   is dropped, so at phone width it still reaches both edges as the design
   draws it. */
#certificate-wrap .cert-banner__inner {
    padding-left: 0;
    padding-right: 0;
}

/* Scaled by width, so it keeps its ratio at any size. */
#certificate-wrap .cert-banner__image {
    display: block;
    width: 100%;
    height: auto;
}

#certificate-wrap .cert-section__inner {
    display: flex;
    flex-direction: column;
    gap: var(--space-lg);
    padding-top: var(--padding-lg);
    padding-bottom: var(--padding-xl);
}

#certificate-wrap .cert-block {
    display: flex;
    flex-direction: column;
    gap: var(--space-xs);
}

/* Heading with a rule that fills the rest of the line. The rule is a flex
   sibling drawn by ::after, so it always stops exactly at the text. */
#certificate-wrap .cert-heading {
    display: flex;
    align-items: center;
    gap: var(--space-xs);
    margin: 0;
    font-family: var(--font-base);
    font-size: var(--fs-title);
    font-weight: var(--fw-bold);
    line-height: var(--lh-body);
    letter-spacing: var(--ls-normal);
    color: var(--color-text-brand);
}

#certificate-wrap .cert-heading__text {
    flex: 0 0 auto;
}

#certificate-wrap .cert-heading::after {
    flex: 1 1 auto;
    height: 0;
    content: "";
    border-top: var(--size-hairline) solid var(--color-line-brand);
}

#certificate-wrap .cert-lead {
    font-size: var(--fs-body);
    font-weight: var(--fw-regular);
    line-height: var(--lh-body);
    color: var(--color-text);
}

/* ---- the facts box --------------------------------------------------------------
   A description list: each row is one label and its value, which is what a
   screen reader should hear rather than two unrelated columns of text.
   ------------------------------------------------------------------------------ */
#certificate-wrap .cert-facts {
    display: flex;
    flex-direction: column;
    gap: var(--space-xs);
    margin: 0;
    margin-top: var(--space-xs);
    padding: var(--padding-sm);
    background-color: var(--color-bg-accent);
    border-radius: var(--radius-md);
}

#certificate-wrap .cert-facts__row {
    display: flex;
    align-items: flex-start;
    gap: var(--space-4);
}

#certificate-wrap .cert-facts__label {
    flex: 0 0 8rem;
    font-size: var(--fs-label);
    font-weight: var(--fw-medium);
    line-height: var(--lh-body);
    color: var(--color-text-brand);
}

#certificate-wrap .cert-facts__value {
    flex: 1 1 auto;
    min-width: 0;
    margin: 0;
    font-size: var(--fs-body);
    font-weight: var(--fw-regular);
    line-height: var(--lh-body);
    color: var(--color-text);
    word-break: keep-all;
}

/* ---- 정부24 hand-off -------------------------------------------------------------
   The shared .button carries the colour, the hover and the block width; only
   the things this screen sizes differently are set here.
   ------------------------------------------------------------------------------ */
#certificate-wrap .cert-cta {
    gap: var(--space-4);
    margin-top: var(--space-xs);
    padding: var(--padding-sm) var(--padding-lg);
    font-size: var(--fs-title);
    line-height: var(--lh-heading);
    border-radius: var(--radius-lg);
}

#certificate-wrap .cert-cta__arrow {
    flex: 0 0 auto;
    width: 2.2rem;
    height: 2.2rem;
}

/* ---- 용어사전 결과 -------------------------------------------------------------
   Figma "Terms_검색_전체화면". The count strip here is tinted (--color-bg-soft),
   unlike the place-search one which is white - that difference is in the two
   designs, not an accident here.
   ------------------------------------------------------------------------------ */
/* The tinted count strip, shared by 용어사전 and 저장. The place-search one is
   white in its own design, so it keeps its own rule. */
/* The tint is on .inner, not on the section: the section is full-bleed, so the
   band ran the whole window while the count text and its hairline stopped at
   the content width. Both now end together. */
.app-count > .inner {
    padding-top: var(--padding-sm);
    padding-bottom: var(--padding-sm);
    background-color: var(--color-bg-soft);
    border-bottom: var(--size-hairline) solid var(--color-line);
}

.app-count__text {
    font-size: var(--fs-label);
    font-weight: var(--fw-medium);
    line-height: var(--lh-body);
    color: var(--color-text-sub);
}

#glossary-wrap .glossary-results__list {
    margin: 0;
    padding: 0;
    list-style: none;
}

#glossary-wrap .glossary-term__inner {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-xs);
    padding-top: var(--padding-md);
    padding-bottom: var(--padding-md);
    /* The divider sits on .inner so it stops where the content stops - see the
       note on #search-wrap .search-result__inner. */
    border-bottom: var(--size-hairline) solid var(--color-line);
}

#glossary-wrap .glossary-term__name {
    margin: 0;
    font-family: var(--font-base);
    font-size: var(--fs-title);
    font-weight: var(--fw-bold);
    line-height: var(--lh-body);
    letter-spacing: var(--ls-normal);
    color: var(--color-text-brand);
}

/* Figma gives every definition a two-line box: a longer one is cut with an
   ellipsis rather than pushing the row taller. */
#glossary-wrap .glossary-term__body {
    display: -webkit-box;
    overflow: hidden;
    font-size: var(--fs-body);
    font-weight: var(--fw-regular);
    line-height: var(--lh-body);
    color: var(--color-text);
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
}

/* "쉽게 바꾼 말" chip - the easy-word tag --color-bg-accent is named for.
   common.css spaces consecutive paragraphs with a top margin; here the column
   gap already does that, so the margin would double it. */
#glossary-wrap .glossary-term__easy {
    margin-top: 0;
    display: inline-flex;
    align-items: center;
    gap: var(--space-4);
    padding: 0.3rem var(--padding-xs);
    font-size: var(--fs-label);
    line-height: var(--lh-body);
    background-color: var(--color-bg-accent);
    border-radius: var(--radius-xs);
}

#glossary-wrap .glossary-term__easy-label {
    font-weight: var(--fw-regular);
    color: var(--color-text);
}

#glossary-wrap .glossary-term__easy-value {
    font-weight: var(--fw-semibold);
    color: var(--color-text-brand);
}

/* ---- pagination -----------------------------------------------------------------
   Shared, not glossary-only: the 저장 list will want the same control.

   The vocabulary follows busan-bus so the two projects read alike -
   .app-pagination / __numbers / __button / __button--current / __ellipsis,
   and a step that cannot be taken is marked aria-disabled rather than being
   given a dead href. One .app-pagination__button covers both the arrows and
   the numbers; they are the same 32px box.

   The geometry is this app's own Figma: 16px around the bar, 16px between the
   three groups, 8px between numbers and 4px inside each arrow pair.
   --------------------------------------------------------------------------------- */
.app-pagination__inner {
    display: flex;
    /* Last-resort guard: below about 300px the ten slots of the widest case
       cannot sit on one line at any sensible size, so they wrap instead of
       spilling out of the bar. */
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: var(--space-xs);
    padding-top: var(--padding-md);
    padding-bottom: var(--padding-md);
}

.app-pagination__edge {
    display: flex;
    align-items: center;
    gap: var(--space-4);
}

.app-pagination__numbers {
    display: flex;
    align-items: center;
    gap: var(--space-xs);
}

/* Figma's 32px box, kept at the 393px design width and allowed to come down
   to 24px below it. The widest case - both ellipses showing, as on any middle
   page - needs ten slots, which a fixed 32px cannot fit on a 360px phone. */
.app-pagination__button {
    display: inline-flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: center;
    min-width: clamp(2.4rem, calc(1.002rem + 5.594vw), 3.2rem);
    height: clamp(2.4rem, calc(1.002rem + 5.594vw), 3.2rem);
    font-family: inherit;
    font-size: var(--fs-caption);
    font-weight: var(--fw-semibold);
    line-height: var(--lh-body);
    color: var(--color-text);
    /* Page links are server-rendered <a>, so the underline has to go. */
    text-decoration: none;
    background-color: var(--color-bg);
    border: 0;
    border-radius: var(--radius-md);
    cursor: pointer;
}

/* The ellipsis is a mark between numbers, not a target: no box, no 32px slot.
   Giving it one is what pushed the middle-page bar past the screen.

   Its three dots sit on the baseline, so in a row of 32px page buttons they
   land about 3px below where the digits' centres are - measured, the digits
   centre exactly on the row and the dots do not - and the row reads as if the
   numbers had floated up. Lifting the glyph by a quarter of its em puts the
   dots on the same line as the middle of the digits. */
.app-pagination__ellipsis {
    display: inline-flex;
    flex: 0 0 auto;
    align-items: center;
    font-size: var(--fs-caption);
    font-weight: var(--fw-semibold);
    line-height: var(--lh-body);
    color: var(--color-text);
    cursor: default;
}

/* A step that would leave the range keeps its place and stops being a target. */
.app-pagination__button[aria-disabled="true"],
.app-pagination__button[disabled] {
    opacity: 0.4;
    cursor: default;
}

.app-pagination__button--current {
    color: var(--color-text-brand);
    background-color: var(--color-bg-accent);
    cursor: default;
}


/* The whole result row is the link to the term, so it needs the block layout
   and none of a link's default colour or underline. */
#glossary-wrap a.glossary-term__inner {
    color: inherit;
    text-decoration: none;
}

/* ---- 용어 상세 ------------------------------------------------------------------
   Figma "Terms_검색_상세". No search bar: the screen is one term. 쉽게 바꾼 말 is
   drawn only when the term has one and 연관어 keeps its heading either way, so
   both versions of the design come out of the same markup.
   ------------------------------------------------------------------------------ */
#glossary-detail-wrap {
    display: flex;
    flex-direction: column;
    padding-top: 0;
    padding-bottom: calc(var(--footer-height) + var(--safe-bottom));
}

#glossary-detail-wrap > .page-title {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

#glossary-detail-wrap .term__inner {
    display: flex;
    flex-direction: column;
    gap: 1.8rem;
    padding-top: var(--padding-xl);
    padding-bottom: var(--padding-xl);
}

#glossary-detail-wrap .term__head {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
}

#glossary-detail-wrap .term__eyebrow,
#glossary-detail-wrap .term__label {
    margin: 0;
    font-family: var(--font-base);
    font-size: var(--fs-label);
    font-weight: var(--fw-semibold);
    line-height: var(--lh-body);
    letter-spacing: var(--ls-normal);
    color: var(--color-text-brand);
}

#glossary-detail-wrap .term__name {
    margin: 0;
    font-family: var(--font-base);
    font-size: var(--fs-page-title);
    font-weight: var(--fw-semibold);
    line-height: var(--lh-body);
    letter-spacing: var(--ls-normal);
    color: var(--color-text);
}

/* 쉽게 바꾼 말 - the same accent the search row's chip uses, at panel size. */
#glossary-detail-wrap .term__easy {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    padding: var(--padding-sm);
    background-color: var(--color-bg-accent);
    border-radius: var(--radius-md);
}

#glossary-detail-wrap .term__easy-label {
    font-size: var(--fs-label);
    font-weight: var(--fw-regular);
    line-height: var(--lh-body);
    color: var(--color-text);
}

/* common.css spaces consecutive paragraphs with a top margin; the column gap
   already does that here, so the margin would add 16px the design has not. */
#glossary-detail-wrap .term__easy-value {
    margin-top: 0;
    font-size: var(--fs-body);
    font-weight: var(--fw-semibold);
    line-height: var(--lh-body);
    color: var(--color-text-brand);
}

/* Every block below the term carries its own closing rule. */
#glossary-detail-wrap .term__block {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    padding-bottom: var(--padding-sm);
    border-bottom: var(--size-hairline) solid var(--color-line);
}

#glossary-detail-wrap .term__body {
    font-size: var(--fs-body);
    font-weight: var(--fw-regular);
    line-height: var(--lh-body);
    color: var(--color-text);
}

/* 관련 정보: a label column and its value, read as pairs rather than as two
   unrelated columns. */
#glossary-detail-wrap .term__facts {
    display: flex;
    flex-direction: column;
    margin: 0;
}

#glossary-detail-wrap .term__facts-row {
    display: flex;
    align-items: flex-start;
    gap: var(--space-4);
}

#glossary-detail-wrap .term__facts-label {
    flex: 0 0 10rem;
    font-size: var(--fs-body);
    font-weight: var(--fw-regular);
    line-height: var(--lh-body);
    color: var(--color-text-sub);
}

#glossary-detail-wrap .term__facts-value {
    flex: 1 1 auto;
    min-width: 0;
    margin: 0;
    font-size: var(--fs-body);
    font-weight: var(--fw-regular);
    line-height: var(--lh-body);
    color: var(--color-text);
}

/* 연관어: pills that wrap, each one a link to that term. */
#glossary-detail-wrap .term__related {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-sm);
    margin: 0;
    padding: 0;
    list-style: none;
}

/* Figma's chip padding is 6px 12px. The 6px is written out rather than taken
   from --padding-6, whose 10px floor is for touch targets: the chip is already
   38px tall, and the floor would push it to 46px. */
#glossary-detail-wrap .term__chip {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0.6rem var(--padding-sm);
    font-size: var(--fs-body);
    font-weight: var(--fw-regular);
    line-height: var(--lh-body);
    color: var(--color-text);
    text-decoration: none;
    background-color: var(--color-bg-soft);
    border: var(--size-hairline) solid var(--color-line-strong);
    border-radius: var(--radius-pill);
}

#glossary-detail-wrap .term__source {
    font-size: var(--fs-caption);
    font-weight: var(--fw-light);
    line-height: var(--lh-body);
    letter-spacing: var(--ls-tight);
    color: var(--color-text-sub);
    text-align: right;
}

/* A 연관어 the published data names but does not contain: shown, not linked. */
#glossary-detail-wrap .term__chip.is-off {
    color: var(--color-text-sub);
    background-color: var(--color-bg);
}

/* ---- 저장 목록 -------------------------------------------------------------------
   Figma "Saved_기본_전체화면". One row per saved parcel: the address as the
   heading, three label/value facts under it, and a more-menu on the right.
   ------------------------------------------------------------------------------ */
#saved-wrap .saved-list__items {
    margin: 0;
    padding: 0;
    list-style: none;
}

#saved-wrap .saved-item__inner {
    position: relative;
    display: flex;
    align-items: flex-start;
    gap: var(--space-4);
    padding-top: var(--padding-md);
    padding-bottom: var(--padding-md);
    /* The divider sits on .inner so it stops where the content stops - see the
       note on #search-wrap .search-result__inner. */
    border-bottom: var(--size-hairline) solid var(--color-line);
}

/* The one thing the ⋮ opens. Figma "Frame 427321913": 62x38, 6px/16px padding,
   a hairline box on white with a soft shadow, and the word in --color-error.
   Anchored under the ⋮ rather than centred on it, which is where the design
   puts it - its right edge lines up with the row's own right edge. */
#saved-wrap .saved-item__menu {
    position: absolute;
    top: 4.7rem;
    /* Its right edge lines up with the row text, not with the screen: the
       containing block here is the .inner padding box, so the gutter has to be
       put back by hand. */
    right: var(--inner-padding-x);
    z-index: var(--z-float);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0.6rem var(--padding-md);
    background-color: var(--color-bg);
    /* Figma Gray/300 #E1E1E1, which is --color-line. --color-line-strong sounds
       like the stronger of the two but is the brand-tinted #c1cdf0, and reads
       blue against white. */
    border: var(--size-hairline) solid var(--color-line);
    border-radius: 0.5rem;
    box-shadow: 0 0 0.4rem rgba(0, 0, 0, 0.15);
}

#saved-wrap .saved-item__menu[hidden] {
    display: none;
}

#saved-wrap .saved-item__delete {
    padding: 0;
    font-family: var(--font-base);
    font-size: var(--fs-body);
    font-weight: var(--fw-regular);
    line-height: var(--lh-body);
    letter-spacing: var(--ls-tight);
    color: var(--color-danger);
    white-space: nowrap;
    background: none;
    border: 0;
    cursor: pointer;
}

/* The row opens the 필지 on the map. It is a link so the back button and "open
   in a new tab" work, but it reads as a row, not as linked text. */
#saved-wrap .saved-item__open {
    color: inherit;
    text-decoration: none;
}

#saved-wrap .saved-item__open:hover {
    color: inherit;
}

#saved-wrap .saved-item__body {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    gap: var(--space-4);
    min-width: 0;
}

#saved-wrap .saved-item__title {
    margin: 0;
    font-family: var(--font-base);
    font-size: var(--fs-title);
    font-weight: var(--fw-bold);
    line-height: var(--lh-body);
    letter-spacing: var(--ls-normal);
    color: var(--color-text-brand);
}

/* The facts read as label/value pairs, not as two loose columns. */
#saved-wrap .saved-item__facts {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    margin: 0;
}

#saved-wrap .saved-item__row {
    display: flex;
    align-items: center;
    gap: var(--space-4);
}

#saved-wrap .saved-item__label {
    flex: 0 0 8rem;
    font-size: var(--fs-label);
    font-weight: var(--fw-medium);
    line-height: var(--lh-body);
    letter-spacing: var(--ls-tight);
    color: var(--color-text-brand);
}

#saved-wrap .saved-item__value {
    flex: 1 1 auto;
    min-width: 0;
    margin: 0;
    font-size: var(--fs-body);
    font-weight: var(--fw-regular);
    line-height: var(--lh-body);
    color: var(--color-text);
}

#saved-wrap .saved-item__more {
    display: inline-flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: center;
    width: var(--icon-size);
    height: var(--icon-size);
    padding: 0;
    color: var(--color-text-brand);
    background: none;
    border: 0;
    cursor: pointer;
}

#saved-wrap .saved-item__more-icon {
    width: var(--icon-size);
    height: var(--icon-size);
}

/* ---- ad slots -----------------------------------------------------------------
   layout.css gives .ad-slot width:100% and no ceiling, so on anything wider than
   the content the banner ran the full window - 1280px of ad against 760px of
   page. Capped here rather than there because layout.css is shared by every
   project built from this template.

   Matching .inner exactly: the same max-width and auto margins, so the ad lines
   up with the cards above and below it. No side padding, which is what keeps it
   edge to edge on a phone - .inner's padding is for text, not for a banner. */
.ad-slot {
    max-width: var(--inner-max-width);
    /* layout.css gives every slot --space-lg above and below. Here the banner
       is meant to sit flush against what it follows - the header, the search
       bar, the card - so the vertical part is dropped and only the centring
       is kept. layout.css is shared by every project from this template, so
       the change belongs here rather than there. */
    margin-top: 0;
    margin-bottom: 0;
    margin-left: auto;
    margin-right: auto;
}

/* The ad that sits between cards is a list item in a <ul>, so it must not carry
   the list's own card styling - only the slot's spacing. */
.search-results__list > .ad-slot-item,
.glossary-results__list > .ad-slot-item,
.saved-list__items > .ad-slot-item {
    list-style: none;
}

/* ---- bottom sheet: gestures ---------------------------------------------------
   A downward drag the sheet did not take used to reach the browser, which read
   it as "pull to refresh" and reloaded the page out from under the person. No
   screen in this app wants that gesture. */
html,
body {
    overscroll-behavior-y: none;
}

/* contain does two jobs here. It stops this scroller chaining to the page, and
   - because the browser then has nothing to do with an overscroll at the top -
   it leaves that gesture to the sheet's own pointer handler. That is what lets
   a pull down from the content close the sheet instead of doing nothing. */
.land-sheet .land-sheet__body {
    overscroll-behavior: contain;
}

/* The grip was 25px tall, under what a finger reliably hits, and in the full
   state it was most of what could start a drag. The bar drawn inside keeps its
   size; only the area that listens grows, to the 44px a touch target wants. */
.land-sheet .land-sheet__handle {
    min-height: 4.4rem;
    align-items: center;
    padding: var(--padding-sm) 0;
}

/* Scrolled to the very top, a downward drag cannot scroll anything, so it is
   handed to the sheet instead - that is the gesture that closes it. pan-down
   leaves the other direction with the browser, so scrolling the content still
   feels native. (pan-down, not pan-up: the value names the direction the
   content travels, which is the opposite of the finger. Measured, not read
   off the spec.) Browsers without it ignore the declaration and keep
   touch-action auto: the content scrolls as before and the sheet is still
   dragged by its grip and head, which is the behaviour this replaces. */
.land-sheet .land-sheet__body[data-at-top] {
    touch-action: pan-down;
}

/* ---- map gestures -------------------------------------------------------------
   ol.css ships `.ol-viewport { touch-action: pan-x pan-y }` so a map embedded in
   a scrolling page does not swallow the page scroll. This map is the page, so
   that default handed every vertical drag to the browser instead of to
   OpenLayers: the map would not pan up or down, and because there is nothing
   to scroll the gesture became a pull-to-refresh.

   none gives the whole gesture to OpenLayers, which is what pans, pinches and
   flicks the map. The id raises specificity above ol.css, which loads after
   this file. */
#home-wrap .ol-viewport {
    touch-action: none;
}
