/* Outfit, self-hosted.

   Served from our own origin, never from fonts.gstatic.com: loading a webfont
   from Google's CDN transmits the visitor's IP address to Google, which German
   courts have held requires consent (LG München I, 3 O 17493/20). A heading
   face is not worth a consent gate - least of all on the landing page, whose
   whole job is to not lose the visitor - and self-hosting removes the question
   entirely. See docs/design/landing_page.md §5.

   Two files, both variable across the whole 100-900 weight axis, so every
   weight the design uses costs one download rather than one per weight. The
   unicode-range split is Google's own: a visitor who never renders an accented
   character never fetches the latin-ext file. Licensed under the SIL Open Font
   License 1.1 - app/assets/fonts/OFL.txt travels with the font files, which is
   what that license asks for.

   Outfit has no CJK coverage, so anything Chinese falls through to the next
   family in the stack whatever this says. It is a face for headings, not for
   the characters the site teaches. */
@font-face {
    font-family: 'Outfit';
    font-style: normal;
    font-weight: 100 900;
    font-display: swap;
    src: url("/assets/outfit-latin-ext-e51c1c3c.woff2") format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
    font-family: 'Outfit';
    font-style: normal;
    font-weight: 100 900;
    font-display: swap;
    src: url("/assets/outfit-latin-c0a8d111.woff2") format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
    --bs-primary: #5942c1;
    --bs-primary-rgb: 89, 66, 193;
    --bs-link-color: var(--bs-primary);
    --bs-link-hover-color: #5b58e6;
    --bs-pagination-active-bg: #ab96d3;

    --primary: var(--bs-primary);
    --gray-100: #f8f9fa;
    --gray-200: #e9ecef;
    --gray-300: #dee2e6;
    --gray-400: #ced4da;
    --gray-500: #adb5bd;
    --gray-600: #6c757d;
    --gray-700: #495057;
    --gray-800: #343a40;
    --gray-900: #212529;
    --gray-850: #2a2d2f;
    --border-width: 1px;
    --border-color: var(--gray-300);
    --border-radius: 0.375rem;
}

.btn-primary {
    --bs-btn-color: #fff !important;
    --bs-btn-hover-color: #fff !important;
    --bs-btn-active-color: #fff !important;
    --bs-btn-bg: var(--bs-primary) !important;
    --bs-btn-border-color: var(--bs-primary) !important;
    --bs-btn-hover-bg: var(--bs-link-hover-color) !important;
    --bs-btn-hover-border-color: var(--bs-link-hover-color) !important;
    --bs-btn-active-bg: var(--bs-link-hover-color) !important;
    --bs-btn-active-border-color: var(--bs-link-hover-color) !important;
    --bs-btn-disabled-bg: var(--bs-primary) !important;
    --bs-btn-disabled-border-color: var(--bs-primary) !important;
}

.btn-outline-primary {
    --bs-btn-color: var(--bs-primary) !important;
    --bs-btn-border-color: var(--bs-primary) !important;
    --bs-btn-hover-bg: var(--bs-primary) !important;
    --bs-btn-hover-border-color: var(--bs-primary) !important;
    --bs-btn-hover-color: #fff !important;
    --bs-btn-active-bg: var(--bs-primary) !important;
    --bs-btn-active-border-color: var(--bs-primary) !important;
    --bs-btn-active-color: #fff !important;
}

.progress-bar {
    background-color: var(--bs-primary) !important;
}

.border-top {
    border-top: var(--border-width) solid var(--border-color);
}

.border-bottom {
    border-bottom: var(--border-width) solid var(--border-color);
}

.border-top-0 {
    border-top: 0 !important;
}

.border-bottom-0 {
    border-bottom: 0 !important;
}

.border-x {
    border-left: var(--border-width) solid var(--border-color);
    border-right: var(--border-width) solid var(--border-color);
}

.border-y {
    border-top: var(--border-width) solid var(--border-color);
    border-bottom: var(--border-width) solid var(--border-color);
}

.rounded-top {
    border-top-left-radius: var(--border-radius);
    border-top-right-radius: var(--border-radius);
}

.rounded-bottom {
    border-bottom-left-radius: var(--border-radius);
    border-bottom-right-radius: var(--border-radius);
}

.rounded-top-0 {
    border-top-left-radius: 0 !important;
    border-top-right-radius: 0 !important;
}

.rounded-bottom-0 {
    border-bottom-left-radius: 0 !important;
    border-bottom-right-radius: 0 !important;
}

@media (min-width: 576px) {
    .border-sm-top {
        border-top: var(--border-width) solid var(--border-color);
    }

    .border-sm-bottom {
        border-bottom: var(--border-width) solid var(--border-color);
    }

    .border-sm-top-0 {
        border-top: 0 !important;
    }

    .border-sm-bottom-0 {
        border-bottom: 0 !important;
    }

    .border-sm-x {
        border-left: var(--border-width) solid var(--border-color);
        border-right: var(--border-width) solid var(--border-color);
    }

    .border-sm-y {
        border-top: var(--border-width) solid var(--border-color);
        border-bottom: var(--border-width) solid var(--border-color);
    }

    .rounded-sm-top {
        border-top-left-radius: var(--border-radius);
        border-top-right-radius: var(--border-radius);
    }

    .rounded-sm-bottom {
        border-bottom-left-radius: var(--border-radius);
        border-bottom-right-radius: var(--border-radius);
    }

    .rounded-sm-top-0 {
        border-top-left-radius: 0 !important;
        border-top-right-radius: 0 !important;
    }

    .rounded-sm-bottom-0 {
        border-bottom-left-radius: 0 !important;
        border-bottom-right-radius: 0 !important;
    }
}

@media (min-width: 768px) {
    .border-md-top {
        border-top: var(--border-width) solid var(--border-color);
    }

    .border-md-bottom {
        border-bottom: var(--border-width) solid var(--border-color);
    }

    .border-md-top-0 {
        border-top: 0 !important;
    }

    .border-md-bottom-0 {
        border-bottom: 0 !important;
    }

    .border-md-x {
        border-left: var(--border-width) solid var(--border-color);
        border-right: var(--border-width) solid var(--border-color);
    }

    .border-md-y {
        border-top: var(--border-width) solid var(--border-color);
        border-bottom: var(--border-width) solid var(--border-color);
    }

    .rounded-md-top {
        border-top-left-radius: var(--border-radius);
        border-top-right-radius: var(--border-radius);
    }

    .rounded-md-bottom {
        border-bottom-left-radius: var(--border-radius);
        border-bottom-right-radius: var(--border-radius);
    }

    .rounded-md-top-0 {
        border-top-left-radius: 0 !important;
        border-top-right-radius: 0 !important;
    }

    .rounded-md-bottom-0 {
        border-bottom-left-radius: 0 !important;
        border-bottom-right-radius: 0 !important;
    }
}

@media (min-width: 992px) {
    .border-lg-top {
        border-top: var(--border-width) solid var(--border-color);
    }

    .border-lg-bottom {
        border-bottom: var(--border-width) solid var(--border-color);
    }

    .border-lg-top-0 {
        border-top: 0 !important;
    }

    .border-lg-bottom-0 {
        border-bottom: 0 !important;
    }

    .border-lg-x {
        border-left: var(--border-width) solid var(--border-color);
        border-right: var(--border-width) solid var(--border-color);
    }

    .border-lg-y {
        border-top: var(--border-width) solid var(--border-color);
        border-bottom: var(--border-width) solid var(--border-color);
    }

    .rounded-lg-top {
        border-top-left-radius: var(--border-radius);
        border-top-right-radius: var(--border-radius);
    }

    .rounded-lg-bottom {
        border-bottom-left-radius: var(--border-radius);
        border-bottom-right-radius: var(--border-radius);
    }

    .rounded-lg-top-0 {
        border-top-left-radius: 0 !important;
        border-top-right-radius: 0 !important;
    }

    .rounded-lg-bottom-0 {
        border-bottom-left-radius: 0 !important;
        border-bottom-right-radius: 0 !important;
    }
}

@media (min-width: 1200px) {
    .border-xl-top {
        border-top: var(--border-width) solid var(--border-color);
    }

    .border-xl-bottom {
        border-bottom: var(--border-width) solid var(--border-color);
    }

    .border-xl-top-0 {
        border-top: 0 !important;
    }

    .border-xl-bottom-0 {
        border-bottom: 0 !important;
    }

    .border-xl-x {
        border-left: var(--border-width) solid var(--border-color);
        border-right: var(--border-width) solid var(--border-color);
    }

    .border-xl-y {
        border-top: var(--border-width) solid var(--border-color);
        border-bottom: var(--border-width) solid var(--border-color);
    }

    .rounded-xl-top {
        border-top-left-radius: var(--border-radius);
        border-top-right-radius: var(--border-radius);
    }

    .rounded-xl-bottom {
        border-bottom-left-radius: var(--border-radius);
        border-bottom-right-radius: var(--border-radius);
    }

    .rounded-xl-top-0 {
        border-top-left-radius: 0 !important;
        border-top-right-radius: 0 !important;
    }

    .rounded-xl-bottom-0 {
        border-bottom-left-radius: 0 !important;
        border-bottom-right-radius: 0 !important;
    }
}

@media (min-width: 1400px) {
    .border-xxl-top {
        border-top: var(--border-width) solid var(--border-color);
    }

    .border-xxl-bottom {
        border-bottom: var(--border-width) solid var(--border-color);
    }

    .border-xxl-top-0 {
        border-top: 0 !important;
    }

    .border-xxl-bottom-0 {
        border-bottom: 0 !important;
    }

    .border-xxl-x {
        border-left: var(--border-width) solid var(--border-color);
        border-right: var(--border-width) solid var(--border-color);
    }

    .border-xxl-y {
        border-top: var(--border-width) solid var(--border-color);
        border-bottom: var(--border-width) solid var(--border-color);
    }

    .rounded-xxl-top {
        border-top-left-radius: var(--border-radius);
        border-top-right-radius: var(--border-radius);
    }

    .rounded-xxl-bottom {
        border-bottom-left-radius: var(--border-radius);
        border-bottom-right-radius: var(--border-radius);
    }

    .rounded-xxl-top-0 {
        border-top-left-radius: 0 !important;
        border-top-right-radius: 0 !important;
    }

    .rounded-xxl-bottom-0 {
        border-bottom-left-radius: 0 !important;
        border-bottom-right-radius: 0 !important;
    }
}

a:not(.btn):not(.nav-link):not(.page-link),
a:not(.btn):not(.nav-link):not(.page-link):link,
a:not(.btn):not(.nav-link):not(.page-link):visited,
a:not(.btn):not(.nav-link):not(.page-link):active {
    color: var(--bs-primary);
    text-decoration: none;
}

a:not(.btn):not(.nav-link):not(.page-link):hover {
    color: var(--bs-link-hover-color);
    text-decoration: underline;
}

.color1,
.color1:hover {
    color: red !important;
}

.color2,
.color2:hover {
    color: #d80 !important;
}

.color3,
.color3:hover {
    color: green !important;
}

.color4,
.color4:hover {
    color: blue !important;
}

.color5,
.color5:hover {
    color: gray !important;
}

.uncolored,
a.uncolored,
a:link.uncolored,
a:visited.uncolored,
a:hover.uncolored,
a:active.uncolored {
    color: unset !important;
}

.bg-gray-100 {
    background-color: var(--gray-100);
}

.bg-gray-200 {
    background-color: var(--gray-200);
}

.bg-gray-300 {
    background-color: var(--gray-300);
}

.bg-gray-400 {
    background-color: var(--gray-400);
}

.bg-gray-500 {
    background-color: var(--gray-500);
}

.bg-gray-600 {
    background-color: var(--gray-600);
}

.bg-gray-700 {
    background-color: var(--gray-700);
}

.bg-gray-800 {
    background-color: var(--gray-800);
}

.bg-gray-850 {
    background-color: var(--gray-850);
}

.bg-gray-900 {
    background-color: var(--gray-900);
}

.border-gray-800 {
    border-color: var(--gray-800) !important;
}

.border-black {
    border-color: #000000 !important;
}

.bg-tile {
    background-image: url("/assets/bg_tile-0229beb0.png");
}

a.external:after {
    color: var(--primary);
    padding-left: 2px;
    content: "🛪";
}

.pagination {
    --bs-pagination-active-bg: var(--bs-pagination-active-bg);
    --bs-pagination-active-border-color: var(--bs-pagination-active-bg);
    --bs-pagination-active-color: var(--bs-primary);
    --bs-pagination-color: var(--bs-primary);
    --bs-pagination-hover-color: var(--bs-link-hover-color);
}



/* Place all the styles related to the hanzi tree here. */

.hanzi_tree {
    text-align: center;
    overflow-x: auto;
    padding: 20px 0;
    width: 100%;
    display: block;
}

.hanzi_tree ul {
    display: inline-flex;
    flex-direction: row;
    justify-content: center;
    align-items: flex-start;
    padding-top: 24px;
    position: relative;
    margin: 0;
    padding-left: 0;
}

.hanzi_tree>ul {
    padding-top: 0;
}

/* Downward line from parent */
.hanzi_tree ul::before {
    content: '';
    position: absolute;
    top: 0;
    left: 50%;
    border-left: 2px solid var(--gray-500);
    width: 0;
    height: 24px;
}

.hanzi_tree>ul::before {
    display: none;
}

.hanzi_tree li {
    display: flex;
    flex-direction: column;
    align-items: center;
    position: relative;
    padding: 24px 4px 0px 4px;
    list-style-type: none;
    margin: 0;
}

/* Horizontal connectors between sibling nodes */
.hanzi_tree li::before,
.hanzi_tree li::after {
    content: '';
    position: absolute;
    top: 0;
    right: 50%;
    border-top: 2px solid var(--gray-500);
    width: 50%;
    height: 24px;
    box-sizing: border-box;
}

.hanzi_tree li::after {
    right: auto;
    left: 50%;
}

/* Vertical line going up from child center */
.hanzi_tree li::before {
    border-right: 2px solid var(--gray-500);
}

/* Remove connectors for single children */
.hanzi_tree li:only-child::after,
.hanzi_tree li:only-child::before {
    display: none;
}

.hanzi_tree li:only-child {
    padding-top: 0;
}

/* Remove horizontal line to the left of first child */
.hanzi_tree li:first-child::before {
    border-top: 0 none;
}

/* Remove horizontal line to the right of last child */
.hanzi_tree li:last-child::after {
    border-top: 0 none;
}

/* Inside element styling */
.hanzi_tree li span {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    text-decoration: none;
    background: transparent;
    transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
    z-index: 10;
    padding: 2px 4px;
}

.hanzi_tree li span:hover {
    /* transform: scale(1.15); */
}

.hanzi_tree li span a {
    color: var(--bs-primary);
    text-decoration: none;
    /* font-weight: bold; */
    transition: color 0.2s ease-in-out;
}

.hanzi_tree li span a:hover {
    text-decoration: none;
    color: var(--bs-link-hover-color, #4338ca);
}

/* Alternative decomposition intermediate node */
.hanzi_tree .alt-node {
    background: linear-gradient(135deg, var(--gray-100) 0%, var(--gray-200) 100%);
    border: 1px dashed var(--bs-primary) !important;
    border-radius: 16px;
    padding: 2px 10px;
    font-size: 0.75rem;
    font-weight: 600;
    color: var(--bs-primary);
    box-shadow: none;
    min-width: unset;
    height: auto;
    display: inline-block;
    text-align: center;
    z-index: 10;
}

.hanzi_tree .alt-node.canon {
    background: linear-gradient(135deg, #e8e5f8 0%, #dcd7f5 100%);
    border: 1px solid var(--bs-primary) !important;
    color: var(--bs-primary);
}

.hanzi_tree .alt-node.alt-circle {
    width: 14px;
    height: 14px;
    min-width: 14px;
    padding: 0;
    border-radius: 50% !important;
    background: var(--bs-primary);
    border: 2px solid var(--bs-body-bg) !important;
    box-shadow: 0 0 0 1px var(--bs-primary);
    z-index: 10;
    display: inline-flex;
    margin: 4px 0;
}

.hanzi_tree .tree-dec-buttons {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    z-index: 20;
    position: relative;
}

.hanzi_tree .btn-dec {
    background: var(--bs-body-bg);
    border: 1px dashed var(--bs-primary);
    border-radius: 16px;
    padding: 2px 8px;
    font-size: 0.75rem;
    font-weight: 600;
    color: var(--bs-primary);
    cursor: pointer;
    transition: all 0.2s ease-in-out;
    outline: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

.hanzi_tree .btn-dec:hover {
    background: var(--gray-100);
    border-color: var(--bs-primary);
}

.hanzi_tree .btn-dec.active {
    background: var(--bs-primary);
    border: 1px solid var(--bs-primary);
    color: #fff;
}

.hanzi_tree ul.progress-vertical-ul {
    display: inline-flex !important;
    flex-direction: column !important;
    align-items: center !important;
    padding-top: 0 !important;
    padding-left: 0 !important;
    position: relative;
    margin: 0;
}

.hanzi_tree ul.progress-vertical-ul::before {
    display: none !important;
}

.hanzi_tree li.marilyn-column {
    padding-left: 0 !important;
    padding-right: 0 !important;
}

.hanzi_tree ul.progress-vertical-ul>li {
    padding: 16px 0 0 0 !important;
    display: flex !important;
    flex-direction: column !important;
    align-items: center !important;
    position: relative;
    list-style-type: none;
    margin: 0;
}

.hanzi_tree ul.progress-vertical-ul>li::before {
    display: none !important;
}

.hanzi_tree ul.progress-vertical-ul>li::after {
    content: '' !important;
    display: block !important;
    position: absolute !important;
    top: 0 !important;
    right: 50% !important;
    border-right: 2px solid var(--gray-500) !important;
    height: 16px !important;
    width: 0 !important;
    z-index: 1;
}


.spinner-sm {
    width: 20px;
    height: 20px;
}

.spinner-image {
    max-width: 100%;
    height: auto;
}

.text-truncate-4 {
    display: -webkit-box;
    -webkit-line-clamp: 4;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* AI Image Cards and Grid */
.ai-image-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    gap: 1.5rem;
    margin-bottom: 2rem;
}

.ai-image-card {
    transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
    border: 1px solid var(--gray-200);
    overflow: hidden;
    background: var(--bs-body-bg);
}

.ai-image-card:hover {
    /* transform: translateY(-4px); */
    box-shadow: 0 0.5rem 1.5rem rgba(0, 0, 0, 0.08) !important;
}

.ai-image-card .card-img-wrapper {
    position: relative;
    width: 100%;
    padding-top: 100%;
    /* 1:1 Aspect Ratio */
    background-color: var(--gray-100);
    overflow: hidden;
}

.ai-image-card .card-img-wrapper img,
.ai-image-card .card-img-wrapper .placeholder-spinner {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.ai-image-batch-badge {
    position: absolute;
    top: 0.75rem;
    left: 0.75rem;
    background: rgba(255, 193, 7, 0.9);
    /* yellow warning */
    backdrop-filter: blur(4px);
    color: #000;
    font-size: 0.75rem;
    padding: 0.25rem 0.5rem;
    border-radius: 20px;
    font-weight: 500;
}

@keyframes pulse {
    0% {
        box-shadow: 0 0 0 0 rgba(89, 66, 193, 0.4);
    }

    70% {
        box-shadow: 0 0 0 10px rgba(89, 66, 193, 0);
    }

    100% {
        box-shadow: 0 0 0 0 rgba(89, 66, 193, 0);
    }
}

.pulsing-border {
    animation: pulse 2s infinite;
}

@keyframes confetti-fall {
    0% {
        transform: translateY(-100vh) rotate(0deg);
        opacity: 1;
    }

    100% {
        transform: translateY(100vh) rotate(720deg);
        opacity: 0;
    }
}

.confetti-piece {
    position: fixed;
    width: 10px;
    height: 10px;
    top: -10px;
    z-index: 9999;
    animation: confetti-fall 3s ease-in-out forwards;
}

.hover-scale {
    transition: transform 0.2s ease-in-out;
}

.hover-scale:hover {
    transform: scale(1.05);
}

/* Linear Timeline Styles */
.sticky-timeline {
    position: sticky;
    top: var(--navbar-height);
    z-index: 1020;
}

/* review/reviews/index: the filter panel's disclosure row. */
#reviews-filters > summary {
    cursor: pointer;
}

/* application/_navbar: the fixed bar's height, which the page has to keep clear
   of - the same 56px whether the bar is expanded or collapsed, as long as it
   stays one row. Measured in the browser rather than derived, and re-measured if
   the toggler, the Reviews button or the search form changes size. */
:root {
    --navbar-height: 56px;
}

/* layouts/application: the in-flow strip above the content already clears part
   of the fixed top bar, so this pad is less than its height. */
.navbar-clearance {
    padding-top: 3rem;
}

/* application/_navbar: on phones (below Bootstrap's md breakpoint) the bar
   sits at the bottom of the viewport, thumb-height, with the hamburger on the
   left, the Reviews button on the right, and the menu opening upward. The nav
   keeps fixed-top in the markup and is re-anchored here, so nothing branches in
   Ruby and #nav-reviews-counter - the target of the review broadcasts - is
   still rendered exactly once.

   From md up to xxl the collapsed bar stays at the top. The bar expands only at
   xxl (navbar-expand-xxl) because, signed in, it needs up to about 1350px on
   one row. The Reviews button must not wrap (text-nowrap) - squeezed, it breaks
   onto two lines and the bar grows to 78px - so the search box gives up width
   instead, down to its 100px minimum. Once expanded the Reviews button
   stays at the far left. Centring it between the two link groups was
   considered and dropped: at 1400px they leave almost no free space, so it
   would only sit a gap away from its neighbours. */
@media (max-width: 767.98px) {
    .navbar.fixed-top {
        top: auto;
        bottom: 0;
        /* The open menu is a 100%-wide flex item on its own line; wrap-reverse
           stacks lines from the bottom up, so it lands above the bar row
           instead of below it, off-screen. */
        flex-wrap: wrap-reverse;
    }

    .navbar.fixed-top .navbar-toggler {
        order: 0;
    }

    .navbar.fixed-top #nav-reviews-counter {
        order: 1;
        margin-left: auto;
    }

    /* order: 2 is load-bearing: at the default 0 the open menu sorts between
       the toggler and the Reviews button, and the button ends up on a line of
       its own above the menu. dvh rather than vh, because on mobile browsers
       100vh is the height with their toolbars hidden. */
    .navbar.fixed-top .navbar-collapse {
        order: 2;
        max-height: calc(100dvh - 4rem);
        overflow-y: auto;
    }

    .navbar-clearance {
        padding-top: 0;
        padding-bottom: var(--navbar-height);
    }

    .sticky-timeline {
        top: 0;
    }

    /* layouts/_cookie_consent: the banner is fixed-bottom at the nav's z-index
       and tall, so it would cover the bar until answered. A first-time phone
       visitor faced with a nav they cannot tap is worse off than one whose
       banner sits a bar's height higher. */
    #cookie-consent-banner {
        bottom: var(--navbar-height);
    }
}

.no-scrollbar::-webkit-scrollbar {
    display: none;
}

.no-scrollbar {
    -ms-overflow-style: none;
    scrollbar-width: none;
}

@keyframes pulse-timeline {
    0% {
        box-shadow: 0 0 0 0 rgba(89, 66, 193, 0.4);
    }

    70% {
        box-shadow: 0 0 0 8px rgba(89, 66, 193, 0);
    }

    100% {
        box-shadow: 0 0 0 0 rgba(89, 66, 193, 0);
    }
}

/* Linear Timeline Responsiveness */
.timeline-wrapper {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.25rem 0;
    transition: max-height 0.3s ease-in-out;
}

.timeline-wrapper:not(.is-expanded) {
    flex-wrap: nowrap;
    overflow: hidden;
    max-height: 38px;
}

.timeline-wrapper:not(.is-expanded) .btn-success {
    display: none !important;
}

.timeline-wrapper.is-expanded {
    flex-wrap: wrap;
    max-height: 500px;
}

/* Learning-target cards: the three suggestions on the home page
   (static_pages/_landing_targets) and the single "make this your target" card
   on a character or comic page. These used to be an inline style plus a :css
   block inside the home page's partial, which only worked while the card lived
   on one page. */
.target-card {
    background: linear-gradient(135deg, var(--bs-body-bg) 0%, var(--bs-tertiary-bg) 100%);
    transition: transform 0.2s, box-shadow 0.2s;
}

.target-card:hover {
    transform: translateY(-5px);
    box-shadow: 0 .5rem 1.5rem rgba(0, 0, 0, .15) !important;
}

/* Decorative only, and knowingly so: this element holds a single Chinese
   character, and Outfit carries no CJK glyphs, so the browser falls through to
   the sans-serif behind it either way. Kept because the fallback is what the
   card has always rendered - and because deleting the rule would invite
   somebody to "restore" it as a real change later. */
.target-card-hanzi {
    font-family: 'Outfit', sans-serif;
}

/* A block of explanatory prose above a section: kept to a readable measure
   rather than the full container width. */
.lead-prose {
    max-width: 40rem;
}

/* ---------------------------------------------------------------------------
   Landing page (static_pages/_landing*)

   The page a visitor with no learning target sees at "/". The design and the
   reasoning behind it are in docs/design/landing_page.md; the
   rules that matter to this stylesheet:

   - Three accent colours carry one idea each, and the same three are used by
     the hero's prose, the annotations over the artwork and nothing else. They
     are declared once here so a change cannot land in one place only.
   - Sections are transparent and the cards inside them are white, rather than
     each section being a card: the layout already puts this page inside a
     container on a grey body, and banding every section reads as noise.
--------------------------------------------------------------------------- */
/* Outfit is the landing page's heading face and nothing else's. It is a
   display face: it earns its place on eight headings a stranger reads once,
   and would only slow down the pages a learner uses every day. The body text
   stays on the system stack, which costs nothing and matches the rest of the
   site. Every heading below is Latin-only, so the 32 KB latin subset covers
   the whole page. */
.landing-hero-title,
.landing-h2,
.landing-step-title,
.landing-tile-title,
.landing-price-title,
.landing-closer-title {
    font-family: 'Outfit', system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    letter-spacing: -0.015em;
}

.landing {
    --landing-person: var(--bs-primary);
    --landing-place: #b45309;
    --landing-part: #2e7d32;
    --landing-tint: #f4f1fb;
    --landing-card-shadow: 0 .25rem 1rem rgba(33, 37, 41, .07);
}

/* Hero ---------------------------------------------------------------- */
.landing-hero {
    padding: 1rem 0 2.5rem;
}

.landing-hero-title {
    font-weight: 700;
    font-size: clamp(2rem, 1.2rem + 2.6vw, 3.25rem);
    line-height: 1.12;
    text-wrap: balance;
    margin-bottom: 1rem;
}

.landing-hero-lead {
    font-size: 1.15rem;
    line-height: 1.6;
    color: var(--gray-700);
    max-width: 34rem;
    margin-bottom: 1.5rem;
}

.landing-key-person {
    color: var(--landing-person);
}

.landing-key-place {
    color: var(--landing-place);
}

.landing-key-part {
    color: var(--landing-part);
}

.landing-hero-trust {
    display: flex;
    align-items: center;
    gap: .5rem;
    color: var(--gray-600);
    font-size: .9375rem;
    margin-bottom: 0;
}

.landing-tick {
    color: var(--landing-part);
    flex-shrink: 0;
}

/* Hero artwork and its annotations ------------------------------------- */
.landing-figure {
    position: relative;
    margin: 0 0 1rem;
}

.landing-figure-frame {
    border-radius: 1rem;
    overflow: hidden;
    box-shadow: 0 .75rem 2.5rem rgba(33, 37, 41, .18);
}

.landing-figure-img {
    display: block;
    width: 100%;
    height: auto;
}

.landing-annotations {
    display: flex;
    flex-direction: column;
    gap: .5rem;
    margin: 1rem 0 0;
}

.landing-annotation {
    display: flex;
    align-items: baseline;
    gap: .5rem;
    font-size: .9375rem;
    line-height: 1.35;
}

.landing-annotation-dot {
    display: inline-block;
    width: .5625rem;
    height: .5625rem;
    border-radius: 50%;
    flex-shrink: 0;
    transform: translateY(-.0625rem);
}

.landing-annotation-dot--person {
    background: var(--landing-person);
}

.landing-annotation-dot--place {
    background: var(--landing-place);
}

.landing-annotation-dot--part {
    background: var(--landing-part);
}

/* Wide enough to lay the annotations over the artwork they point at. Below
   this they stay a plain legend under the image: at narrow widths the chips
   cover the drawing they are explaining. The offsets are hand-placed against
   app/assets/images/landing/de5_comic.webp and have to be redone if that
   image is ever replaced. */
@media (min-width: 992px) {
    .landing-annotations {
        position: absolute;
        inset: 0;
        display: block;
        margin: 0;
    }

    .landing-annotation {
        position: absolute;
        align-items: center;
        max-width: 62%;
        background: rgba(255, 255, 255, .95);
        border-radius: 2rem;
        padding: .3125rem .75rem;
        font-size: .8125rem;
        box-shadow: 0 .125rem .5rem rgba(0, 0, 0, .2);
    }

    .landing-annotation--person {
        top: 4%;
        left: 3%;
    }

    .landing-annotation--bai {
        top: 4%;
        right: 3%;
    }

    .landing-annotation--shao {
        top: 46%;
        left: 3%;
    }

    .landing-annotation--place {
        bottom: 4%;
        left: 50%;
        transform: translateX(-50%);
        max-width: 80%;
    }
}

/* 白 + 勺 = 的, under the artwork. */
.landing-equation {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: .625rem;
    background: var(--bs-body-bg, #fff);
    border: var(--border-width) solid var(--border-color);
    border-radius: .75rem;
    padding: .875rem 1.25rem;
    box-shadow: var(--landing-card-shadow);
}

.landing-equation-part {
    font-size: 1.75rem;
    font-weight: 600;
    color: var(--landing-part);
    line-height: 1;
}

.landing-equation-op {
    color: var(--gray-500);
}

.landing-equation-target {
    font-size: 2.25rem;
    font-weight: 700;
    color: var(--landing-person);
    line-height: 1;
}

.landing-equation-gloss {
    color: var(--gray-600);
    font-size: .9375rem;
}

/* Sections -------------------------------------------------------------- */
.landing-section {
    padding: 2.5rem 0;
}

.landing-section--tinted {
    background: var(--landing-tint);
    border-radius: 1rem;
    padding: 2.5rem 1.5rem;
    margin: 1rem 0;
}

.landing-section-head {
    text-align: center;
    max-width: 44rem;
    margin: 0 auto 2rem;
}

.landing-h2 {
    font-weight: 700;
    font-size: clamp(1.5rem, 1.1rem + 1.3vw, 2.125rem);
    margin-bottom: .75rem;
    text-wrap: balance;
}

.landing-section-sub,
.landing-body {
    color: var(--gray-700);
    line-height: 1.6;
    margin-bottom: 0;
}

.landing-body + .landing-body {
    margin-top: 1rem;
}

.landing-section-closer {
    text-align: center;
    color: var(--gray-700);
    margin: 2rem 0 0;
}

/* The three method steps ------------------------------------------------ */
.landing-step {
    height: 100%;
    background: var(--bs-body-bg, #fff);
    border-radius: 1rem;
    padding: 1.75rem;
    box-shadow: var(--landing-card-shadow);
}

.landing-step-head {
    display: flex;
    align-items: center;
    gap: .75rem;
    margin-bottom: .875rem;
}

.landing-step-number {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    flex-shrink: 0;
    border-radius: 50%;
    background: var(--bs-primary);
    color: #fff;
    font-weight: 700;
}

.landing-step-title {
    font-size: 1.1875rem;
    font-weight: 600;
    margin: 0;
}

.landing-step-body {
    color: var(--gray-700);
    line-height: 1.6;
    margin-bottom: 0;
}

/* Route preview --------------------------------------------------------- */
.landing-route-card {
    background: var(--bs-body-bg, #fff);
    border-radius: 1rem;
    padding: 1.75rem;
    box-shadow: var(--landing-card-shadow);
}

.landing-route-label {
    text-transform: uppercase;
    letter-spacing: .04em;
    font-size: .8125rem;
    font-weight: 600;
    color: var(--gray-600);
    margin-bottom: 1rem;
}

.landing-route-pills {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .5rem;
}

/* Every pill in the real timeline is a link; here they illustrate one, and
   the only thing worth clicking on this page is the target picker. */
.landing-route-pill {
    cursor: default;
    pointer-events: none;
}

.landing-route-ellipsis {
    color: var(--gray-500);
    letter-spacing: .1em;
}

.landing-route-caption {
    color: var(--gray-700);
    font-size: .9375rem;
    line-height: 1.55;
    margin: 1rem 0 0;
}

.landing-route-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 1rem;
    margin: 1rem 0 0;
    font-size: .8125rem;
    color: var(--gray-600);
}

.landing-route-legend li {
    display: flex;
    align-items: center;
    gap: .375rem;
}

.landing-legend-swatch {
    display: inline-block;
    width: .625rem;
    height: .625rem;
    border-radius: 50%;
}

.landing-legend-swatch--done {
    background: var(--bs-success, #198754);
}

.landing-legend-swatch--active {
    background: var(--bs-primary);
}

.landing-legend-swatch--ahead {
    border: var(--border-width) solid var(--gray-400);
}

/* Depth tiles ----------------------------------------------------------- */
.landing-tile {
    height: 100%;
    background: var(--bs-body-bg, #fff);
    border-radius: 1rem;
    padding: 1.5rem;
    box-shadow: var(--landing-card-shadow);
}

.landing-tile-icon {
    color: var(--bs-primary);
    margin-bottom: .75rem;
}

.landing-tile-title {
    font-size: 1.0625rem;
    font-weight: 600;
    margin-bottom: .5rem;
}

.landing-tile-body {
    color: var(--gray-700);
    font-size: .9375rem;
    line-height: 1.55;
    margin-bottom: 0;
}

/* Pricing --------------------------------------------------------------- */
.landing-price {
    height: 100%;
    border: var(--border-width) solid var(--border-color);
    border-radius: 1rem;
    padding: 1.75rem;
    background: var(--bs-body-bg, #fff);
}

.landing-price--paid {
    border: 2px solid var(--bs-primary);
    background: var(--landing-tint);
}

.landing-price-title {
    font-weight: 700;
    font-size: 1.3125rem;
    margin-bottom: .75rem;
}

.landing-price-body {
    color: var(--gray-700);
    line-height: 1.6;
    margin-bottom: 0;
}

/* Closing band ---------------------------------------------------------- */
.landing-closer {
    text-align: center;
    border-radius: 1rem;
    padding: 3rem 1.5rem;
    margin: 1rem 0 2rem;
    background: linear-gradient(180deg, var(--bs-primary) 0%, #47339e 100%);
    color: #fff;
}

.landing-closer-title {
    font-weight: 700;
    font-size: clamp(1.5rem, 1.1rem + 1.4vw, 2.25rem);
    margin-bottom: .75rem;
    text-wrap: balance;
}

.landing-closer-sub {
    color: rgba(255, 255, 255, .85);
    margin-bottom: 1.5rem;
}

.landing-closer-btn {
    background: #fff;
    color: var(--bs-primary);
}

.landing-closer-btn:hover,
.landing-closer-btn:focus {
    background: var(--gray-100);
    color: var(--bs-link-hover-color);
}

/* Comic archive (/comics) ------------------------------------------------
   A grid of identically sized 100x100 tiles, one per comic, each a crop of
   that comic's last panel. The crop is done here rather than by Active
   Storage: the markup asks for the [180, 135] variant every panel blob
   already carries, and object-fit does the squaring, so no page view
   commissions a MiniMagick transform. The oversized source also keeps the
   tile sharp on a hidpi screen. See docs/technical-references/comics_archive.md. */
.comic-archive-title {
    font-weight: 600;
    letter-spacing: -0.01em;
    margin: 2rem 0 .75rem;
}

.comic-archive-lead {
    color: var(--gray-600);
    max-width: 40rem;
    margin-bottom: 2rem;
}

.comic-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, 100px);
    /* start, not center: auto-fill lays down as many 100px tracks as the row
       holds, so centring would only shift the whole grid by the leftover
       remainder and leave it a few pixels out of line with the heading. */
    justify-content: start;
    gap: .75rem;
    list-style: none;
    padding: 0;
    margin: 0 0 2rem;
}

.comic-tile {
    position: relative;
    display: block;
    width: 100px;
    height: 100px;
    overflow: hidden;
    border-radius: .5rem;
    background: var(--gray-100);
    /* The hairline ring is the load-bearing half: a good many panels are drawn on
       a white or near-white background, and without it those tiles dissolve into
       the page and the grid stops reading as a grid. */
    box-shadow: 0 0 0 1px rgba(33, 37, 41, .10), 0 1px 2px rgba(33, 37, 41, .10);
    transition: transform .18s ease, box-shadow .18s ease;
}

.comic-tile:hover,
.comic-tile:focus-visible {
    transform: translateY(-2px);
    box-shadow: 0 0 0 1px rgba(89, 66, 193, .35), 0 .5rem 1rem rgba(89, 66, 193, .28);
}

.comic-tile:focus-visible {
    outline: 2px solid var(--bs-primary);
    outline-offset: 2px;
}

.comic-tile-image {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: transform .3s ease;
}

.comic-tile:hover .comic-tile-image {
    transform: scale(1.06);
}

/* The caption repeats what the link already announces, so it is aria-hidden and
   purely an invitation to click: hidden until the tile is hovered or focused. */
.comic-tile-label {
    position: absolute;
    inset: auto 0 0 0;
    padding: 1rem .25rem .3rem;
    background: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, .75) 65%);
    color: #fff;
    font-size: .9375rem;
    line-height: 1.2;
    text-align: center;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    opacity: 0;
    transform: translateY(100%);
    transition: opacity .18s ease, transform .18s ease;
}

.comic-tile:hover .comic-tile-label,
.comic-tile:focus-visible .comic-tile-label {
    opacity: 1;
    transform: none;
}

/* A tile whose panel image is missing keeps the footprint - dropping it would
   make the grid disagree with the paginator - and shows the caption instead. */
.comic-tile-fallback {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    padding: .25rem;
    border: 1px dashed var(--gray-400);
    border-radius: .5rem;
    color: var(--gray-600);
    font-size: 1.25rem;
    line-height: 1.1;
    text-align: center;
    overflow: hidden;
}

/* No hover to reveal the caption on a touch screen, so it stays up. */
@media (hover: none) {
    .comic-tile-label {
        opacity: 1;
        transform: none;
    }
}

@media (prefers-reduced-motion: reduce) {

    .comic-tile,
    .comic-tile-image,
    .comic-tile-label {
        transition: none;
    }

    .comic-tile:hover,
    .comic-tile:focus-visible {
        transform: none;
    }

    .comic-tile:hover .comic-tile-image {
        transform: none;
    }
}

/* Reviews feed: a card is passed when the visitor scrolls it up into the top
   quarter of the viewport (see scroll_pass_controller.js), which is confirmed by
   the checkmark popping in and the card itself dimming. */

.review-item {
    position: relative;
}

.review-pass-badge {
    position: absolute;
    /* Centred on the card's own height. The translate has to travel with the
       pop-in scale below, since one transform property carries both. */
    top: 50%;
    right: 0.5rem;
    z-index: 2;
    font-size: 1.5rem;
    line-height: 1;
    pointer-events: none;
    visibility: hidden;
    opacity: 0;
    transform: translateY(-50%) scale(0.5);
    transition: opacity 0.3s ease, transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1), visibility 0s linear 0.3s;
}

.review-passed .review-pass-badge {
    visibility: visible;
    opacity: 1;
    transform: translateY(-50%) scale(1);
    transition: opacity 0.3s ease, transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.review-item-body {
    transition: opacity 0.35s ease;
}

.review-passed .review-item-body {
    opacity: 0.55;
}

/* Room below the last review so it can be scrolled up into the top quarter of
   the screen, which is what passes it, before the congratulations message.
   Must stay at or above (1 - PASS_AT_FRACTION) of a screen - see
   scroll_pass_controller.js - or the last card can never reach the line. */
.review-scroll-spacer {
    min-height: 75vh;
}

/* The ad unit (shared/_adsense). Its height is reserved up front so the
   spinner-to-ad swap does not push everything below it down the page (CLS).
   280px is what this unit requests at desktop width (format=1200x280 in a
   1400px-wide browser, observed 2026-09-10); narrower screens have not been
   measured. A container that ad_slot_controller.js hides reserves nothing. */
.ad-container {
    min-height: 280px;
}

/* Fixed-value styles for individual templates. haml-lint's InlineStyles linter keeps them out of
   style attributes; only values computed per render stay inline. */

.mw-240 {
    max-width: 240px;
}

/* course/courses/_form, course/units/_form: the sortable drag handle. */
.drag-handle {
    cursor: grab;
    font-size: 1.2rem;
}

@keyframes fadeIn {
    from {
        opacity: 0;
    }

    to {
        opacity: 1;
    }
}

/* course/units/_batch_completed: the batch-result alert. */
.fade-in {
    animation: fadeIn 0.5s;
}

/* collection/collections/_form, collection/sentences/_form, comments/_form,
   translations/_errors: the plain (non-Bootstrap) validation error box. */
.form-error-box {
    color: red;
}

.celebration-overlay {
    position: fixed;
    top: 0;
    left: 0;
    width: 100vw;
    height: 100vh;
    background: linear-gradient(135deg, rgba(17, 153, 142, 0.96) 0%, rgba(56, 239, 125, 0.96) 100%);
    color: white;
    z-index: 9990;
    overflow-y: auto;
}

.celebration-card {
    background: rgba(255, 255, 255, 0.1);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    border: 1px solid rgba(255, 255, 255, 0.2);
    max-width: 600px;
    width: 90%;
    box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.2);
}

.celebration-hanzi {
    text-shadow: 0 0 20px rgba(255, 255, 255, 0.4);
    font-size: 8rem;
    line-height: 1;
}

/* dashboards/_progress_tree_node: a node's own character/label. */
.tree-node-label {
    font-size: 1.3em;
}

.tree-node-pinyins {
    font-size: 1.1em;
    margin-top: 4px;
    z-index: 10;
}

.tutorial-card-border {
    border-color: var(--bs-primary);
    border-width: 2px;
}

/* layouts/application: reserves the sticky-timeline turbo frame's height
   before its content arrives, so the layout does not jump into place. */
.timeline-frame-placeholder {
    min-height: 38px;
}

.next-step-disabled {
    background: #ccc;
    border: none;
    font-weight: bold;
    cursor: not-allowed;
}

/* shared/_progression_guidance: one gradient per guidance state, on the card header and on
   the links that match it. border: none is for the links; the header has no visible border. */
.guidance-header-active {
    background: linear-gradient(135deg, #5942c1 0%, #7c5dfa 100%);
    border: none;
}

.guidance-header-ready {
    background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
    border: none;
}

.guidance-header-prereq {
    background: linear-gradient(135deg, #e65c00 0%, #f9d423 100%);
}

.guidance-header-locked {
    background: linear-gradient(135deg, #4b5563 0%, #1f2937 100%);
}

/* shared/_progression_guidance: the "prerequisite" links share the header's orange. */
.guidance-prereq-link {
    border-color: #e65c00;
    color: #e65c00;
}

.txt-to-image-preview {
    max-width: 256px;
}

.debug-attrs {
    white-space: pre-wrap;
    font-family: monospace;
}

.credit-pack-card {
    min-width: 250px;
}

.chart-container {
    width: 100%;
    height: 280px;
}

.chart-table-scroll {
    max-height: 18rem;
}

.tabular-nums {
    font-variant-numeric: tabular-nums;
}

.stat-tile-label {
    letter-spacing: .04em;
}

.progress-thin {
    height: 12px;
}

/* collection/collections/_form: the video-upload progress bar's starting width,
   before video_upload_progress_controller.js sets an inline width per upload
   event (which overrides this regardless of whether the start is a class or
   inline style). */
.progress-width-0 {
    width: 0%;
}

/* layouts/_linear_timeline: the "More"/collapse toggle button. */
.timeline-toggle-btn {
    white-space: nowrap;
    flex-shrink: 0;
}

/* user_masteries/statistics/_heatmap: the legend swatches. Only the fill
   color varies per bucket, so that part stays inline. */
.legend-swatch {
    display: inline-block;
    width: 11px;
    height: 11px;
    border-radius: 2px;
}

@media (prefers-reduced-motion: reduce) {

    .review-pass-badge,
    .review-item-body {
        transition: none;
    }
}
