/*
 * Component styles — see docs/Design_System.md.
 *
 * Every value here is a semantic token. No literal colours, no raw pixel spacing.
 * Bootstrap does the layout work; this file only supplies the project's own look.
 */

/* --- surfaces ----------------------------------------------------------- */

.ots-surface {
    background-color: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-sm);
    padding: var(--space-5);
}

.ots-navbar,
.ots-footer {
    background-color: var(--color-surface);
    border-color: var(--color-border) !important;
}

.ots-navbar .nav-link {
    color: var(--color-text-secondary);
    font-weight: 600;
}

.ots-navbar .nav-link:hover,
.ots-navbar .nav-link.active {
    color: var(--color-text-accent);
}

.ots-text-muted {
    color: var(--color-text-muted);
    font-size: var(--font-size-meta);
}

/* --- buttons ------------------------------------------------------------ */

/*
 * Bootstrap buttons read --bs-btn-*, not --bs-primary, so each variant is mapped explicitly.
 * The background is green-600 rather than the reference green-500, which reaches only
 * 4.25:1 against white text and would fail the AA threshold required by D-87.
 */
.btn-primary {
    --bs-btn-bg: var(--color-action);
    --bs-btn-border-color: var(--color-action);
    --bs-btn-color: var(--color-text-on-action);
    --bs-btn-hover-bg: var(--color-action-hover);
    --bs-btn-hover-border-color: var(--color-action-hover);
    --bs-btn-hover-color: var(--color-text-on-action);
    --bs-btn-active-bg: var(--color-action-active);
    --bs-btn-active-border-color: var(--color-action-active);
    --bs-btn-disabled-bg: var(--color-action);
    --bs-btn-disabled-border-color: var(--color-action);
}

.btn-outline-secondary {
    --bs-btn-color: var(--color-text);
    --bs-btn-border-color: var(--color-border);
    --bs-btn-hover-bg: var(--color-canvas);
    --bs-btn-hover-border-color: var(--color-border);
    --bs-btn-hover-color: var(--color-text);
    --bs-btn-active-bg: var(--color-canvas);
    --bs-btn-active-color: var(--color-text);
}

.btn-danger {
    --bs-btn-bg: var(--color-danger);
    --bs-btn-border-color: var(--color-danger);
    --bs-btn-color: var(--color-text-on-action);
    --bs-btn-hover-bg: var(--color-danger-text);
    --bs-btn-hover-border-color: var(--color-danger-text);
    --bs-btn-disabled-bg: var(--color-danger);
    --bs-btn-disabled-border-color: var(--color-danger);
}

.btn-ghost {
    --bs-btn-color: var(--color-text-accent);
    --bs-btn-hover-color: var(--color-action-active);
    --bs-btn-bg: transparent;
    --bs-btn-border-color: transparent;
}

/* --- status badge (chapter 52.12) --------------------------------------- */

/*
 * Colour is never the only carrier of meaning: the badge always renders an icon and a word.
 * The icon is aria-hidden in the template because it repeats what the text already says.
 */
.ots-status {
    align-items: center;
    border-radius: var(--radius-pill);
    display: inline-flex;
    font-size: var(--font-size-small);
    font-weight: 600;
    gap: var(--space-2);
    padding: var(--space-1) var(--space-3);
}

.ots-status--online {
    background-color: var(--color-positive-surface);
    color: var(--color-positive-text);
}

.ots-status--offline {
    background-color: var(--color-danger-surface);
    color: var(--color-danger-text);
}

.ots-status--error {
    background-color: var(--color-warning-surface);
    color: var(--color-warning-text);
}

.ots-status--pending {
    background-color: var(--color-muted-surface);
    color: var(--color-muted-text);
}

/*
  A thirty-day change, in the same two colours a status uses. Colour alone never carries it:
  the sign is printed as well, which is what makes the column readable in greyscale and to
  anyone who does not distinguish the two hues (D-85).
*/
.ots-trend--up {
    color: var(--color-positive-text);
    font-weight: 600;
}

.ots-trend--down {
    color: var(--color-danger-text);
    font-weight: 600;
}

/* --- server list -------------------------------------------------------- */

/*
 * Chapter 52.15 forbids horizontal scrolling on a phone, so the same read model renders as
 * a table on wide screens and as cards below the medium breakpoint. Two templates, one
 * dataset — never two sources of data.
 */
.ots-server-table {
    --bs-table-bg: var(--color-surface);

    border-color: var(--color-border);
}

.ots-server-table th {
    color: var(--color-text-muted);
    font-size: var(--font-size-meta);
    font-weight: 600;
    text-transform: uppercase;
}

.ots-server-name {
    color: var(--color-text);
    font-weight: 700;
    text-decoration: none;
}

.ots-server-name:hover {
    color: var(--color-text-accent);
}

/*
  D-110, revised: a bought highlight leads the list and has to be obvious. A gold frame
  and a crown on the badge. The badge still says why, because colour alone is not a label.
*/
.ots-server--highlighted,
.ots-server--highlighted.ots-server--offline {
    background-color: #fff4d6;
    box-shadow: inset 0 0 0 3px #c8922a;
}

tr.ots-server--highlighted td {
    background-color: #fff4d6;
    border-top: 2px solid #c8922a;
    border-bottom: 2px solid #c8922a;
}

tr.ots-server--highlighted td:first-child {
    border-left: 3px solid #c8922a;
}

tr.ots-server--highlighted td:last-child {
    border-right: 3px solid #c8922a;
}

article.ots-server--highlighted {
    border: 3px solid #c8922a;
    box-shadow: 0 0 0 4px rgba(200, 146, 42, 0.22);
}

.ots-tag--promoted {
    background-color: #c8922a;
    color: #1a1203;
    font-weight: 700;
}

/*
  D-112: a launch announcement is its own paid shelf. Green, because gold already means a
  Highlight. The countdown is the thing a visitor came to read, so it carries the accent.
*/
.ots-soon {
    background-color: var(--color-surface-accent);
    box-shadow: inset 0 0 0 2px var(--color-action);
}

.ots-soon-clock {
    display: flex;
    flex-shrink: 0;
    gap: 0.35rem;
}

.ots-soon-box {
    align-items: center;
    background-color: var(--color-surface);
    border-radius: var(--radius-sm);
    box-shadow: inset 0 0 0 1px var(--color-action);
    display: flex;
    flex: 0 0 auto;
    flex-direction: column;
    min-width: 3rem;
    padding: 0.15rem 0.3rem 0.2rem;
}

.ots-soon-digit {
    color: var(--color-text-accent);
    font-size: 1.05rem;
    font-variant-numeric: tabular-nums;
    font-weight: 700;
    line-height: 1.1;
    white-space: nowrap;
}

.ots-soon-label {
    color: var(--color-text-muted);
    font-size: 0.65rem;
    font-weight: 600;
    line-height: 1.1;
    white-space: nowrap;
}

/* D-81: an offline server is toned down, but its status stays fully legible. */
.ots-server--offline {
    background-color: var(--color-muted-surface);
}

.ots-server--offline .ots-server-name,
.ots-server--offline .ots-server-description {
    color: var(--color-text-muted);
}

.ots-server--offline .ots-server-logo {
    filter: grayscale(100%);
    opacity: 0.6;
}

.ots-flag-wrap {
    display: inline-flex;
    line-height: 0;
}

.ots-flag {
    border-radius: 2px;
    box-shadow: inset 0 0 0 1px var(--color-border);
    height: 18px;
    object-fit: cover;
    width: 24px;
}

.ots-stars {
    align-items: center;
    display: inline-flex;
    gap: 0.35rem;
}

.ots-stars-graphic {
    display: inline-block;
    font-size: 1.15rem;
    line-height: 1;
    position: relative;
}

.ots-stars-track {
    color: #d5dbe3;
}

.ots-stars-fill {
    color: #f5b301;
    left: 0;
    overflow: hidden;
    position: absolute;
    top: 0;
    white-space: nowrap;
    width: calc(var(--ots-rating) / 5 * 100%);
}

.ots-stars-value {
    font-variant-numeric: tabular-nums;
    font-weight: 700;
}

.ots-stars-count {
    color: var(--color-text-muted);
    font-size: 0.85rem;
}

.ots-btn-compare {
    background-color: var(--color-text);
    border-color: var(--color-text);
    color: var(--color-text-on-action);
    font-weight: 600;
}

.ots-btn-compare:hover,
.ots-btn-compare:focus {
    background-color: var(--color-text-secondary);
    border-color: var(--color-text-secondary);
    color: var(--color-text-on-action);
}

.ots-owner-reply {
    border-left: 3px solid var(--color-action);
    padding-left: 0.75rem;
}

.ots-gallery {
    display: grid;
    gap: 0.75rem;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
}

.ots-gallery img {
    aspect-ratio: 16 / 9;
    border-radius: var(--radius-md);
    height: auto;
    object-fit: cover;
    width: 100%;
}

.ots-server-logo {
    border-radius: var(--radius-md);
    height: 48px;
    object-fit: cover;
    width: 48px;
}

.ots-tag {
    background-color: var(--color-surface-accent);
    border-radius: var(--radius-sm);
    color: var(--color-text-accent);
    font-size: var(--font-size-meta);
    font-weight: 600;
    padding: var(--space-1) var(--space-2);
}

/* --- empty state and skeleton ------------------------------------------- */

.ots-empty-state {
    color: var(--color-text-muted);
    padding: var(--space-7) var(--space-4);
    text-align: center;
}

.ots-empty-state i {
    color: var(--color-border);
    font-size: var(--font-size-h1);
}

/*
 * A skeleton has the dimensions of the content it replaces. A bare spinner would let the
 * layout jump once data arrives, which chapter 52.16 calls out as a defect.
 */
.ots-skeleton {
    animation: ots-skeleton-pulse 1.4s ease-in-out infinite;
    background-color: var(--color-border);
    border-radius: var(--radius-sm);
    display: block;
}

@keyframes ots-skeleton-pulse {
    0%,
    100% {
        opacity: 1;
    }

    50% {
        opacity: 0.45;
    }
}

@media (prefers-reduced-motion: reduce) {
    .ots-skeleton {
        animation: none;
    }
}

/* --- forms -------------------------------------------------------------- */

.ots-form-label {
    color: var(--color-text);
    font-weight: 600;
    margin-bottom: var(--space-2);
}

.ots-form-hint {
    color: var(--color-text-muted);
    font-size: var(--font-size-meta);
}

.ots-stage-row {
    align-items: center;
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin-bottom: var(--space-2);
}

.ots-stage-row .form-control {
    max-width: 8rem;
}

.ots-form-error {
    align-items: center;
    color: var(--color-danger-text);
    display: flex;
    font-size: var(--font-size-small);
    gap: var(--space-2);
    margin-top: var(--space-2);
}

.form-control:focus,
.form-select:focus {
    border-color: var(--color-action);
    box-shadow: none;
}

/* --- account screens ---------------------------------------------------- */

/*
 * A measure, not a breakpoint. Sign-in and registration are single-task pages, and a line of
 * explanatory text stretched across a desktop window is read twice as slowly as one that is
 * not.
 */
.ots-auth-card {
    max-width: 30rem;
}

.ots-auth-card .ots-surface {
    padding: var(--space-6);
}

/* --- styleguide only ---------------------------------------------------- */

.ots-swatch {
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    height: 56px;
}

/*
 * The chart needs a box with a height of its own: a canvas has no intrinsic size, and
 * Chart.js in responsive mode fills whatever it is given. Taller on wide screens, where a
 * ninety-point series would otherwise be a nearly flat band.
 */
.ots-chart {
    height: 240px;
    position: relative;
}

@media (min-width: 992px) {
    .ots-chart {
        height: 300px;
    }
}

/*
 * Text somebody else wrote: an announcement from us, a post from a server owner. It arrives
 * as markup that the sanitiser has already reduced to a handful of tags, so the rules here
 * only have to give those tags spacing — Bootstrap's reboot strips margins that a paragraph
 * of prose needs back.
 *
 * The heading sizes are capped below the page's own h1 on purpose. The author chooses the
 * structure of their text; they do not get to outshout the title of the page it sits on.
 */
.ots-prose > :last-child {
    margin-bottom: 0;
}

.ots-prose p,
.ots-prose ul,
.ots-prose ol,
.ots-prose blockquote {
    margin-bottom: var(--space-4);
}

.ots-prose h2,
.ots-prose h3 {
    font-size: var(--font-size-h3);
    font-weight: 600;
    margin-bottom: var(--space-3);
    margin-top: var(--space-5);
}

.ots-prose h3 {
    font-size: var(--font-size-body);
}

.ots-prose blockquote {
    border-left: 3px solid var(--color-border);
    color: var(--color-text-secondary);
    padding-left: var(--space-4);
}

/*
 * The filter bar. Nine filters do not fit above a list on a phone, so all but the phrase and
 * the order live in a details element — which collapses without JavaScript and announces
 * itself to a screen reader as the expandable region it is.
 */
.ots-filters-more > summary {
    cursor: pointer;
    padding: 0.25rem 0;
}

.ots-filters-more > summary::marker {
    color: var(--bs-secondary-color);
}

/*
 * The panel's side menu. It is a list of links, so it is marked up as one; the styling only
 * makes the current section obvious and gives every link a target big enough to hit on a
 * phone, where the menu sits above the content rather than beside it.
 */
.ots-dashboard-nav {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
}

.ots-dashboard-link {
    border-radius: var(--radius-md);
    color: var(--color-text-secondary);
    display: block;
    padding: var(--space-2) var(--space-3);
    text-decoration: none;
}

.ots-dashboard-link:hover {
    background-color: var(--color-surface-accent);
    color: var(--color-text-accent);
}

.ots-dashboard-link.active {
    background-color: var(--color-surface-accent);
    color: var(--color-text-accent);
    font-weight: 600;
}

.ots-dashboard-link:focus-visible {
    outline: 2px solid var(--color-focus-ring);
    outline-offset: 2px;
}

/*
 * Pagination. Bootstrap declares its pagination variables on .pagination itself rather than
 * on :root, so a token set alongside the others would lose on specificity and the current
 * page would stay stock blue — the one colour in the interface that means nothing.
 */
/*
 * Comparison (D-130). The list itself must not scroll sideways (chapter 52.15); this table
 * is the exception, because the columns are the servers and a phone cannot show four of them
 * at once. The first column stays put so a scrolled figure still has its label.
 */
.ots-compare {
    overflow-x: auto;
}

.ots-compare table {
    width: max-content;
    min-width: 100%;
}

.ots-compare th:first-child {
    position: sticky;
    left: 0;
    background-color: var(--color-surface);
    z-index: 1;
}

/*
  D-140. One banner, centred under the menu. The file is 728 by 90. A narrow window
  scales the whole creative and keeps that shape; cropping the sides would hide
  whatever was drawn at the edges.
*/
.ots-ad {
    background-color: var(--color-surface);
    border-bottom: 1px solid var(--color-border);
    display: flex;
    justify-content: center;
    padding: 0.5rem 1rem;
}

.ots-ad-link {
    display: block;
    line-height: 0;
    max-width: 100%;
}

.ots-ad-link.is-waiting {
    display: none;
}

.ots-ad img,
.ots-ad-preview {
    aspect-ratio: 728 / 90;
    height: auto;
    max-width: 100%;
    width: 728px;
}

/*
 * The home hero. The picture fades on its left, and the mask continues that fade into
 * the page colour so the illustration does not sit in a box beside the words.
 */
.ots-hero-kicker {
    color: var(--color-text-accent);
    font-size: var(--font-size-meta);
    font-weight: 700;
    letter-spacing: 0.08em;
    margin-bottom: var(--space-3);
    text-transform: uppercase;
}

.ots-hero-title {
    font-size: var(--font-size-h1);
    font-weight: 700;
    letter-spacing: -0.02em;
    line-height: 1.1;
    margin-bottom: var(--space-3);
}

.ots-hero-lead {
    color: var(--color-text-secondary);
    font-size: var(--font-size-body);
    margin-bottom: 0;
    max-width: 36rem;
}

.ots-hero-art {
    border-radius: var(--radius-lg);
    display: block;
    height: auto;
    width: 100%;
}

@media (min-width: 992px) {
    .ots-hero-title {
        font-size: 3rem;
    }

    .ots-hero-art {
        border-radius: 0;
        -webkit-mask-image: linear-gradient(to right, transparent 0%, #000 16%);
        mask-image: linear-gradient(to right, transparent 0%, #000 16%);
    }
}

.pagination {
    --bs-pagination-color: var(--color-text-secondary);
    --bs-pagination-hover-color: var(--color-text-accent);
    --bs-pagination-focus-color: var(--color-text-accent);
    --bs-pagination-active-bg: var(--color-action);
    --bs-pagination-active-border-color: var(--color-action);
}
