.container-header {
    position: relative;
    z-index: 10;
    background-color: #434178;
    background-image: linear-gradient(135deg, #122F49 0%, #F65945 100%);
    box-shadow: 0 5px 5px rgba(0, 0, 0, 0.03) inset;
}

.container-footer {
    z-index: 10;
    background-color: #434178;
    background-image: linear-gradient(135deg, #122F49 0%, #F65945 100%);
    box-shadow: 0 5px 5px rgba(0, 0, 0, 0.03) inset;
}

.twitch .twitch-video {
  padding-top: 56.25%;
  position: relative;
  height: 0;
}

.twitch .twitch-video iframe {
  position: absolute;
  width: 100%;
  height: 100%;
  top: 0;
}

.twitch .twitch-chat {
  height: 400px;
}

.twitch .twitch-chat iframe {
  width: 100%;
  height: 100%;
}

@media screen and (min-width: 850px) {
  .twitch {
    position: relative;
  }

  .twitch .twitch-video {
    width: 75%;
    padding-top: 42.1875%;
  }

  .twitch .twitch-chat {
    width: 25%;
    height: auto;
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
  }
}


/* Nox: Cassiopeia ships `.banner-overlay .overlay { background-color: #00000080 }` —
   a flat 50% black layer that halves the hero artwork's brightness. Replaced with a
   soft vertical gradient: art stays visible, white hero text keeps >= 10:1 contrast. */
.container-banner .banner-overlay .overlay {
    background-color: transparent;
    background-image: linear-gradient(180deg,
        rgba(0, 0, 0, 0.34) 0%,
        rgba(0, 0, 0, 0.16) 20%,
        rgba(0, 0, 0, 0.10) 50%,
        rgba(0, 0, 0, 0.22) 100%);
}

/* Nox: `background-attachment: fixed` makes `cover` size the image against the VIEWPORT
   instead of the banner box, so a 70vh banner crops ~30% of the artwork's height and width.
   Scrolling attachment ties sizing to the element, so the art is framed as designed. */
.container-banner .banner-overlay {
    background-attachment: scroll;
}

/* Nox: header/footer repainted to the hero artwork's palette.
   Sampled from images/banners/hero-homelab-2026.jpg:
     dark ink #00333E, teal field #003743, mid teal #004B52 / #0A5C58,
     arc amber #E1A831, waveform cyan #56B0B0.
   Fills stay between the two dark teals so white nav text keeps ~12.9:1;
   amber is used only as a hairline, because white on amber is 2.1:1. */
.container-header {
    background-color: #00333E;
    background-image: linear-gradient(135deg, #00333E 0%, #004B52 58%, #0A5C58 100%);
    border-bottom: 2px solid #E1A831;
    box-shadow: 0 5px 5px rgba(0, 0, 0, 0.03) inset;
}

.container-footer {
    background-color: #00333E;
    background-image: linear-gradient(135deg, #0A5C58 0%, #004B52 45%, #00333E 100%);
    border-top: 2px solid #E1A831;
    box-shadow: 0 5px 5px rgba(0, 0, 0, 0.03) inset;
}

/* Nox: retarget the theme accent from the old navy (#112855) to the hero palette.
   Cassiopeia reads these from :root in global/colors_standard.css, which loads BEFORE
   user.css, so these win without !important. White on #0A5C58 is ~7.6:1.
   Amber is the hover accent, with dark ink text, because white on amber is only 2.1:1. */
:root {
    --cassiopeia-color-primary: #0A5C58;
    --cassiopeia-color-link: #0A5C58;
    --link-color: #0A5C58;
    --link-color-rgb: 10, 92, 88;
    --cassiopeia-color-hover: #E1A831;
    --link-hover-color: #0C6F68;
    --link-hover-color-rgb: 12, 111, 104;
}

.btn-primary:hover,
.btn-primary:focus {
    color: #04212A;
    border-color: #E1A831;
}

/* Nox: Shorts article pages. The vertical 1080x1920 art was rendering at full column
   width (986x1753), which looked absurd next to 60 words of text. Scoped to the Shorts
   menu id (itemid-120) so blog covers keep their full-width treatment.
   Also removes the double image that occurred when images.image_fulltext duplicated the
   <figure> already inside the intro text. */
.itemid-120 .com-content-article__body figure.post-cover {
    margin: 0.5rem auto 1.75rem;
    max-width: 26rem;
}

.itemid-120 .com-content-article__body figure.post-cover img {
    display: block;
    width: 100%;
    height: auto;
    border-radius: 14px;
    box-shadow: 0 10px 30px rgba(0, 51, 62, 0.18);
}

/* The short's copy is one paragraph; give it a readable measure under the card. */
.itemid-120 .com-content-article__body > p {
    max-width: 42rem;
    font-size: 1.075rem;
}

@media (max-width: 575px) {
    .itemid-120 .com-content-article__body figure.post-cover {
        max-width: 100%;
    }
}

/* Nox: follow-up. A narrow card over a wider paragraph read as an accident, so the
   short now behaves as one centred block: card and copy share the same measure. */
.itemid-120 .com-content-article__body > p {
    max-width: 26rem;
    margin-left: auto;
    margin-right: auto;
}

.itemid-120 .com-content-article__body > .post-cover + p {
    margin-top: 0;
}

/* Nox: About page (itemid-121). The previous body was a single ~960-character paragraph of
   <br>s plus CV boilerplate written in 2024. The new body uses a lede, ruled h2 sections, a
   topic grid and channel pills. Everything is scoped to this one menu item so no other page
   (blog, shorts, terms) is affected. */
.itemid-121 .com-content-article__body > p.about-lede {
    font-size: 1.3rem;
    line-height: 1.6;
    margin-bottom: 1.75rem;
}

/* The content column is 986px wide at a 1440 viewport. Capping prose at 46rem left a 250px
   dead strip down the right of every paragraph, which reads as a layout bug, so the text
   fills the column like every blog post does and the type size carries the measure. */
.itemid-121 .com-content-article__body > p {
    font-size: 1.075rem;
    line-height: 1.75;
}

.itemid-121 .com-content-article__body h2 {
    display: flex;
    align-items: center;
    gap: .85rem;
    font-size: 1rem;
    font-weight: 700;
    letter-spacing: .09em;
    text-transform: uppercase;
    color: #0A5C58;
    margin: 2.5rem 0 1rem;
}

.itemid-121 .com-content-article__body h2::after {
    content: "";
    flex: 1 1 auto;
    height: 1px;
    background: linear-gradient(90deg, rgba(225, 168, 49, .60), rgba(10, 92, 88, .10));
}

.itemid-121 .about-topics {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
    gap: .85rem;
}

.itemid-121 .about-topics li {
    padding: .95rem 1.05rem;
    border: 1px solid rgba(10, 92, 88, .14);
    border-radius: 12px;
    background: rgba(10, 92, 88, .03);
}

.itemid-121 .about-topics strong {
    display: block;
    margin-bottom: .3rem;
    color: #04212A;
    line-height: 1.35;
}

.itemid-121 .about-topics span {
    display: block;
    font-size: .93rem;
    line-height: 1.5;
    color: #4a5b5e;
}

.itemid-121 .about-links {
    list-style: none;
    margin: .9rem 0 0;
    padding: 0;
    display: flex;
    flex-wrap: wrap;
    gap: .6rem;
}

.itemid-121 .about-links a {
    display: inline-flex;
    align-items: baseline;
    gap: .5rem;
    padding: .5rem 1rem;
    border: 1px solid rgba(10, 92, 88, .22);
    border-radius: 999px;
    text-decoration: none;
    line-height: 1.3;
    transition: background-color .15s ease, border-color .15s ease;
}

.itemid-121 .about-links a strong {
    font-weight: 650;
}

.itemid-121 .about-links a span {
    font-size: .85rem;
    color: #55686b;
}

.itemid-121 .about-links a:hover,
.itemid-121 .about-links a:focus {
    background: rgba(225, 168, 49, .14);
    border-color: rgba(225, 168, 49, .65);
    text-decoration: none;
}

.itemid-121 .about-links a:hover span,
.itemid-121 .about-links a:focus span {
    color: #4a5b5e;
}

@media (max-width: 767px) {
    .itemid-121 .com-content-article__body > p.about-lede {
        font-size: 1.15rem;
    }
}

/* Nox: Terms & Privacy page (itemid-126). The old body was 2024 boilerplate for a different
   website: it claimed tracking software, a mailing list and third-party advertising, none of
   which this site has ever had. Rewritten as an honest policy in the same visual language as
   the About page (itemid-121): keep the two blocks in sync if the design changes. */
.itemid-126 .com-content-article__body > p.doc-meta {
    font-size: .78rem;
    letter-spacing: .10em;
    text-transform: uppercase;
    color: #55686b;
    margin: 0 0 1.1rem;
}

.itemid-126 .com-content-article__body > p.about-lede {
    font-size: 1.3rem;
    line-height: 1.6;
    margin-bottom: 1.75rem;
}

.itemid-126 .com-content-article__body > p {
    font-size: 1.075rem;
    line-height: 1.75;
}

.itemid-126 .com-content-article__body h2 {
    display: flex;
    align-items: center;
    gap: .85rem;
    font-size: 1rem;
    font-weight: 700;
    letter-spacing: .09em;
    text-transform: uppercase;
    color: #0A5C58;
    margin: 2.5rem 0 1rem;
    scroll-margin-top: 2rem;
}

.itemid-126 .com-content-article__body h2::after {
    content: "";
    flex: 1 1 auto;
    height: 1px;
    background: linear-gradient(90deg, rgba(225, 168, 49, .60), rgba(10, 92, 88, .10));
}

/* TL;DR card. Reads before the legal text, which is the point of it. */
.itemid-126 .doc-summary {
    margin: 1.75rem 0 2rem;
    padding: 1.05rem 1.3rem 1.15rem;
    background: rgba(10, 92, 88, .045);
    border-left: 3px solid #E1A831;
    border-radius: 0 12px 12px 0;
}

.itemid-126 .doc-summary p {
    margin: 0 0 .45rem;
    font-size: .82rem;
    letter-spacing: .09em;
    text-transform: uppercase;
    color: #0A5C58;
}

.itemid-126 .doc-summary ul {
    margin: 0;
    padding-left: 1.15rem;
}

.itemid-126 .doc-summary li {
    margin-bottom: .3rem;
    line-height: 1.55;
}

.itemid-126 .doc-summary li:last-child {
    margin-bottom: 0;
}

/* Contents. Long policy pages need one; it also proves the page was written for humans. */
.itemid-126 .doc-toc {
    margin: 0 0 1rem;
    padding: .9rem 1.1rem;
    border: 1px solid rgba(10, 92, 88, .14);
    border-radius: 12px;
}

.itemid-126 .doc-toc__label {
    margin: 0 0 .5rem;
    font-size: .78rem;
    letter-spacing: .10em;
    text-transform: uppercase;
    color: #55686b;
}

.itemid-126 .doc-toc ol {
    margin: 0;
    padding: 0;
    list-style: none;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
    gap: .25rem 1.5rem;
    counter-reset: doc;
}

.itemid-126 .doc-toc li {
    counter-increment: doc;
    font-size: .95rem;
}

.itemid-126 .doc-toc li a {
    display: inline-block;
    padding: .15rem 0;
    text-decoration: none;
}

.itemid-126 .doc-toc li a::before {
    content: counter(doc) ". ";
    color: #55686b;
}

.itemid-126 .doc-toc li a:hover,
.itemid-126 .doc-toc li a:focus {
    text-decoration: underline;
}

/* The "not collected" row: negative facts deserve to be scannable too. */
.itemid-126 .doc-nope {
    list-style: none;
    margin: .75rem 0 0;
    padding: 0;
    display: flex;
    flex-wrap: wrap;
    gap: .5rem;
}

.itemid-126 .doc-nope li {
    font-size: .875rem;
    line-height: 1.4;
    padding: .3rem .75rem;
    border-radius: 999px;
    background: rgba(10, 92, 88, .07);
    color: #4a5b5e;
}

@media (max-width: 767px) {
    .itemid-126 .com-content-article__body > p.about-lede {
        font-size: 1.15rem;
    }
}

/* Nox: Help landing (itemid-103). The blog layout was dumping the three static pages in full,
   one after another, complete with the contact form embedded in the middle of a listing. The
   articles are now split intro/full so this page gets teasers only, the menu shows the category
   description, and the result is a three-card index that matches the About topic grid.
   Teasers are line-clamped so three different lengths still make three equal cards. */
.itemid-103 .category-desc {
    margin-bottom: 1.75rem;
}

.itemid-103 .category-desc .about-lede {
    font-size: 1.15rem;
    line-height: 1.6;
    margin-bottom: 0;
}

.itemid-103 .com-content-category-blog__items {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 1rem;
    counter-reset: helpcard;
}

.itemid-103 .blog-item {
    display: flex;
    flex-direction: column;
    margin: 0;
    padding: 1rem 1.1rem 1.1rem;
    border: 1px solid rgba(10, 92, 88, .14);
    border-radius: 14px;
    background: #fff;
    counter-increment: helpcard;
    transition: border-color .15s ease, box-shadow .15s ease, transform .15s ease;
}

.itemid-103 .blog-item:hover {
    border-color: rgba(225, 168, 49, .65);
    box-shadow: 0 10px 26px rgba(0, 51, 62, .10);
    transform: translateY(-2px);
}

.itemid-103 .blog-item::before {
    content: "0" counter(helpcard);
    display: block;
    font-size: .72rem;
    font-weight: 700;
    letter-spacing: .16em;
    color: #55686b;
    padding-bottom: .6rem;
    border-bottom: 1px solid rgba(10, 92, 88, .12);
    margin-bottom: .75rem;
}

.itemid-103 .blog-item .item-content {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
}

.itemid-103 .blog-item .page-header {
    margin: 0 0 .5rem;
    padding: 0;
    border: 0;
}

.itemid-103 .blog-item .page-header h2 {
    margin: 0;
    font-size: 1.15rem;
    line-height: 1.3;
    color: #0A5C58;
}

.itemid-103 .blog-item .item-content > p {
    font-size: .95rem;
    line-height: 1.6;
    color: #33474a;
    margin-bottom: .6rem;
}

.itemid-103 .blog-item .item-content > p.about-lede {
    display: -webkit-box;
    -webkit-line-clamp: 4;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* The date line is useful on the policy itself, not in an index card. */
.itemid-103 .blog-item .doc-meta {
    display: none;
}

/* The Read more button becomes the card's footer link. */
.itemid-103 .blog-item .readmore {
    margin: auto 0 0;
    padding-top: .8rem;
}

.itemid-103 .blog-item .readmore a.btn {
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    padding: .38rem .85rem;
    font-size: .80rem;
    font-weight: 600;
    color: #0A5C58;
    background: rgba(10, 92, 88, .05);
    border: 1px solid rgba(10, 92, 88, .22);
    border-radius: 999px;
    text-decoration: none;
    transition: background-color .15s ease, border-color .15s ease, color .15s ease;
}

.itemid-103 .blog-item .readmore a.btn:hover,
.itemid-103 .blog-item .readmore a.btn:focus {
    color: #04212A;
    background: rgba(225, 168, 49, .18);
    border-color: rgba(225, 168, 49, .70);
}

@media (max-width: 991px) {
    .itemid-103 .com-content-category-blog__items {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

@media (max-width: 575px) {
    .itemid-103 .com-content-category-blog__items {
        grid-template-columns: 1fr;
    }
}

/* Nox: Contacts page (itemid-122). It was the last static page still wearing the 2024 default
   look: one line of text ("Online contact form") and a bare DJ EasyContact form. Same type and
   card language as About (itemid-121) and Terms (itemid-126). */
.itemid-122 .com-content-article__body > p {
    font-size: 1.075rem;
    line-height: 1.75;
}

.itemid-122 .com-content-article__body > p.about-lede {
    font-size: 1.3rem;
    line-height: 1.6;
    margin-bottom: 1.75rem;
}

.itemid-122 .com-content-article__body > .moduletable {
    margin: 1.5rem 0 0;
    padding: 1.4rem 1.5rem;
    border: 1px solid rgba(10, 92, 88, .14);
    border-radius: 14px;
    background: #fff;
}

.itemid-122 .moduletable .dj-simple-contact-form-required-fields-info {
    font-size: .8rem;
    color: #55686b;
    margin-bottom: .9rem;
}

.itemid-122 .moduletable input.form-control,
.itemid-122 .moduletable textarea.form-control {
    border-radius: 10px;
    border-color: rgba(10, 92, 88, .22);
}

.itemid-122 .moduletable input.form-control:focus,
.itemid-122 .moduletable textarea.form-control:focus {
    border-color: rgba(225, 168, 49, .75);
    box-shadow: 0 0 0 .18rem rgba(225, 168, 49, .18);
}

/* The module ships its own orange button (assets/style2.css sets #EB5635 with a 3px radius),
   which fights the teal/amber palette. This selector out-specifies
   .dj-simple-contact-form.style-2 .dj-simple-contact-form.button (0,4,0) without !important. */
.itemid-122 .moduletable .dj-simple-contact-form.style-2 .dj-simple-contact-form.button {
    width: auto;
    min-width: 9rem;
    padding: .58rem 1.6rem;
    border: 1px solid #0A5C58;
    border-radius: 999px;
    background: #0A5C58;
    color: #fff;
    font-family: inherit;
    font-size: .9rem;
    font-weight: 600;
    letter-spacing: .02em;
    text-transform: none;
}

.itemid-122 .moduletable .dj-simple-contact-form.style-2 .dj-simple-contact-form.button:hover,
.itemid-122 .moduletable .dj-simple-contact-form.style-2 .dj-simple-contact-form.button:focus {
    background: #E1A831;
    border-color: #E1A831;
    color: #04212A;
}

.itemid-122 .moduletable .label_terms {
    font-size: .9rem;
    line-height: 1.45;
}

@media (max-width: 767px) {
    .itemid-122 .com-content-article__body > p.about-lede {
        font-size: 1.15rem;
    }
}

/* Nox: card covers in listings (home, /blog, /shorts).
   The publish pipeline stored images.image_intro as a LAN loopback URL
   (http://127.0.0.1:8008/images/...), so every card rendered a broken picture whose alt text was
   the article title: "missing picture and same title". The paths are fixed (relative now), but that
   exposed the real duplication: Joomla renders image_intro as <figure class="item-image"> in a card
   AND the article's introtext already opens with the same file as <figure class="post-cover">.
   Keep the in-text figure as the card picture and drop the images-field rendering here; the field
   still feeds the OG/Twitter share card through plg_system_mrhordaseo. */
.blog-item > figure.item-image,
.blog-item > figure.item-image.left,
figure.newsflash-image {
    display: none;
}

/* ---- language switcher (header top bar + footer) ------------------------- */
.container-topbar {
    display: flex;
    justify-content: flex-end;
    align-items: center;
    padding-block: .1rem;
}

.container-topbar .mod-languages { margin-left: auto; }

.container-topbar .mod-languages .btn-group { display: inline-flex; }

.container-topbar .mod-languages .dropdown-toggle {
    background: none;
    border: 0;
    color: inherit;
    font-size: .78rem;
    letter-spacing: .04em;
    text-transform: uppercase;
    opacity: .75;
    padding: .15rem .4rem;
}

.container-topbar .mod-languages .dropdown-toggle:hover,
.container-topbar .mod-languages .dropdown-toggle:focus {
    opacity: 1;
    background: none;
    box-shadow: none;
}

.container-topbar .mod-languages .dropdown-menu {
    font-size: .85rem;
    min-width: 9rem;
    padding: .25rem 0;
}

.container-topbar .mod-languages .dropdown-menu a { padding: .3rem .8rem; display: block; }

/* footer switcher: quiet inline row, active language marked */
.lang-switch-footer .mod-languages__list.lang-inline {
    display: flex;
    justify-content: center;
    flex-wrap: wrap;
    gap: .35rem 1.1rem;
    list-style: none;
    margin: .35rem 0 0;
    padding: 0;
    font-size: .85rem;
}

.lang-switch-footer .mod-languages__list.lang-inline li { margin: 0; }

.lang-switch-footer .mod-languages__list.lang-inline a {
    color: inherit;
    opacity: .7;
    text-decoration: none;
    border-bottom: 1px solid transparent;
}

.lang-switch-footer .mod-languages__list.lang-inline a:hover { opacity: 1; border-bottom-color: currentColor; }

.lang-switch-footer .mod-languages__list.lang-inline li.lang-active a {
    opacity: 1;
    font-weight: 600;
    border-bottom-color: currentColor;
}

/* footer switcher: moduleclass_sfx is not rendered by Cassiopeia's chrome,
   so target the inline list structurally (only the footer instance uses inline). */
.mod-languages__list.lang-inline {
    display: flex;
    justify-content: center;
    flex-wrap: wrap;
    gap: .35rem 1.1rem;
    list-style: none;
    margin: .35rem 0 0;
    padding: 0;
    font-size: .85rem;
}

.mod-languages__list.lang-inline li { margin: 0; }

.mod-languages__list.lang-inline a {
    color: inherit;
    opacity: .7;
    text-decoration: none;
    border-bottom: 1px solid transparent;
}

.mod-languages__list.lang-inline a:hover { opacity: 1; border-bottom-color: currentColor; }

.mod-languages__list.lang-inline li.lang-active a {
    opacity: 1;
    font-weight: 600;
    border-bottom-color: currentColor;
}

/* Cassiopeia paints .lang-active with a light grey box; keep the footer switcher as plain text */
.mod-languages__list.lang-inline .lang-active,
.mod-languages__list.lang-inline li.lang-active {
    background: none;
}
.mod-languages__list.lang-inline a { padding: .1rem 0; }
