Viewing File: /usr/local/cpanel/base/frontend/jupiter/domains/directives/domainSearchModal.less
// Copyright 2026 WebPros International, LLC
// All rights reserved.
// copyright@cpanel.net http://cpanel.net
// This code is subject to the cPanel license. Unauthorized copying is prohibited.
// Reusable Domain Search modal — used by the Manage Domain
// recommendations banner and by the "Purchase a Domain" option in the
// create-domain chooser. All selectors are namespaced under
// .domain-search-modal / .domain-search-modal-dialog so this component
// can be safely loaded alongside any host application's stylesheet.
@import (reference) "../../libraries/bootstrap/source/less/variables.less";
// The modal hosts the shared <tld-priority-input> directive, so include
// its styles here too. LESS @import is deduped by default, so importing
// this file from index.less alongside the modal does not duplicate rules.
@import "tldPriorityInput.less";
// Skeleton loading animation for domain recommendations. Defined here
// (rather than in the host stylesheet) so the modal stays portable when
// reused outside this app. CSS @keyframes are globally scoped, so the
// domain-recommendations-banner skeleton elements in index.less continue
// to reference the same animation.
@keyframes skeleton-pulse {
0% {
opacity: 0.15;
}
50% {
opacity: 0.3;
}
100% {
opacity: 0.15;
}
}
.domain-search-modal {
position: relative;
display: flex;
flex-direction: column;
background: #fff;
border-radius: 8px;
box-shadow: 0 8px 20px 0 rgba(2, 2, 2, 0.04);
overflow: hidden;
max-height: 80vh;
// Establishes the flex context that lets __results grow and scroll.
// Normally the inner results panel owns the scroll so the header and
// footer stay pinned; the body only scrolls itself when the viewport
// is too small to fit the panel's minimum height (e.g. stacked
// featured cards on narrow screens), instead of clipping the list.
&__body {
display: flex;
flex-direction: column;
flex: 1 1 auto;
min-height: 0;
overflow-y: auto;
}
&__header {
flex-shrink: 0;
background-color: #f8f9fa;
padding: 24px;
display: flex;
flex-direction: column;
gap: 16px;
}
&__title-row {
display: flex;
align-items: flex-start;
gap: 24px;
}
&__title {
flex: 1;
margin: 0;
font-family: Lato, sans-serif;
font-size: 32px;
font-weight: 500;
line-height: 1.2;
color: #212529;
}
// Shared close-button treatment. Standardised across every modal in the
// domain recommendations flow (banner, create-domain chooser, search
// modal) so the close (x) control is the same size and hit area
// everywhere. The 20px icon inside 6px padding yields a ≥32px target,
// comfortably above the WCAG 2.2 (2.5.8) 24px minimum.
&__close {
flex-shrink: 0;
padding: 6px;
background: none;
border: none;
cursor: pointer;
font-size: 20px;
color: #3c4d5a;
line-height: 1;
&:hover,
&:focus-visible {
color: #212529;
}
&:focus-visible {
outline: 2px solid #003da6;
outline-offset: 2px;
}
}
// Success banner: shown when the searched-for domain matched a result
&__success {
flex-shrink: 0;
position: relative;
display: block;
padding: 24px;
padding-right: 174px;
background-color: #f8f9fa;
overflow: hidden;
}
&__success-content {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 8px;
}
&__success-heading {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
}
&__success-check {
flex-shrink: 0;
font-size: 24px;
color: #ff6c37;
}
&__success-title {
margin: 0;
font-family: Lato, sans-serif;
font-size: 32px;
font-weight: 500;
line-height: 1.2;
color: #212529;
word-break: break-word;
}
&__success-subtitle {
margin: 0 0 8px 0;
font-family: Lato, sans-serif;
font-size: 20px;
font-weight: 500;
line-height: 1.2;
color: #212529;
}
&__success-price {
margin: 0;
font-family: Lato, sans-serif;
font-size: 14px;
font-weight: 700;
line-height: 1.5;
color: #020202;
}
&__success-purchase.btn {
align-self: flex-start;
margin-top: 0;
}
&__success-close {
position: absolute;
top: 16px;
right: 16px;
z-index: 2;
padding: 6px;
background: none;
border: none;
cursor: pointer;
font-size: 20px;
line-height: 1;
color: #3c4d5a;
&:hover,
&:focus-visible {
color: #212529;
}
&:focus-visible {
outline: 2px solid #003da6;
outline-offset: 2px;
}
}
&__success-graphic {
position: absolute;
bottom: 0;
right: 0;
width: 150px;
height: 150px;
pointer-events: none;
user-select: none;
}
// Unavailable banner: shown when a fully qualified searched domain is
// not available. Reuses the featured-card grid for the alternatives
// shown below.
&__unavailable {
position: relative;
display: flex;
align-items: flex-start;
gap: 24px;
width: 100%;
}
&__unavailable-content {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 8px;
}
&__unavailable-title {
margin: 0;
font-family: Lato, sans-serif;
font-size: 32px;
font-weight: 500;
line-height: 1.2;
color: #212529;
}
&__unavailable-subtitle {
margin: 0;
font-family: Lato, sans-serif;
font-size: 20px;
font-weight: 500;
line-height: 1.2;
color: #212529;
word-break: break-word;
}
&__unavailable-prompt {
margin: 0;
font-family: Lato, sans-serif;
font-size: 14px;
font-weight: 400;
line-height: 1.5;
color: #020202;
}
&__unavailable-close {
flex-shrink: 0;
}
// Featured recommendation cards
&__featured {
display: flex;
gap: 12px;
}
&__featured-card {
flex: 1;
position: relative;
min-width: 0;
min-height: 109px;
display: flex;
flex-direction: column;
align-items: flex-end;
justify-content: flex-end;
gap: 8px;
padding: 12px 16px;
background: #fff;
border: 1px solid #e7e7e8;
border-radius: 4px;
box-shadow: 0 8px 20px 0 rgba(2, 2, 2, 0.04);
}
&__featured-card--skeleton {
min-height: 109px;
align-items: flex-start;
justify-content: center;
gap: 12px;
}
&__skeleton-block {
display: block;
border-radius: 4px;
animation: skeleton-pulse 1.5s ease-in-out infinite;
&--domain {
width: 180px;
height: 20px;
background-color: #212529;
}
&--price {
width: 120px;
height: 14px;
background-color: #3c4d5a;
animation-delay: 0.15s;
}
&--button {
width: 100px;
height: 32px;
background-color: #e7e7e8;
animation-delay: 0.3s;
}
}
&__premium-badge {
position: absolute;
top: -1px;
right: 19px;
padding: 4px;
background-color: #ffc4af;
border-radius: 0 0 4px 4px;
font-size: 12px;
line-height: 1.5;
color: #020202;
}
&__card-content {
width: 100%;
}
&__card-domain {
display: flex;
align-items: center;
gap: 8px;
font-size: 16px;
color: #212529;
}
&__check-icon {
color: #ff6c37;
font-size: 24px;
flex-shrink: 0;
}
&__domain-name {
font-weight: 400;
strong {
font-weight: 700;
}
}
&__card-price {
margin: 0;
padding-left: 32px;
font-size: 14px;
line-height: 1.5;
color: #212529;
}
// Purchase buttons — need .btn qualifier to beat Bootstrap specificity
&__purchase-btn.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
height: 32px;
padding: 5px 9px;
border-radius: 3.2px;
font-size: 14px;
line-height: 1.5;
white-space: nowrap;
cursor: pointer;
box-shadow: none;
&:focus-visible {
outline: 2px solid #003da6;
outline-offset: 2px;
}
}
&__purchase-btn--accent.btn {
color: #020202;
background: #fff;
border: 1px solid #ff6c37;
&:hover,
&:focus {
background-color: #ffc4af;
border-color: #ff6c37;
color: #020202;
}
}
&__purchase-btn--outline.btn {
color: #003da6;
background: #fff;
border: 1px solid #003da6;
&:hover,
&:focus {
background-color: #ccd8ed;
border-color: #003da6;
color: #003da6;
}
}
// Search section
&__search-section {
flex-shrink: 0;
padding: 16px 24px;
border-top: 1px solid #e7e7e8;
border-bottom: 1px solid #e7e7e8;
}
&__search-label {
display: block;
font-family: Lato, sans-serif;
font-size: 16px;
font-weight: 700;
line-height: 1.5;
color: #212529;
margin-bottom: 4px;
}
&__search-row {
display: flex;
gap: 8px;
align-items: center;
}
&__search-bar {
flex: 1;
position: relative;
display: flex;
align-items: center;
min-width: 0;
}
&__search-icon {
position: absolute;
left: 12px;
font-size: 16px;
color: #3c4d5a;
pointer-events: none;
}
&__search-input {
width: 100%;
padding: 6px 12px 6px 36px;
border: 0.5px solid #3c4d5a;
border-radius: 4px;
font-size: 16px;
line-height: 1.5;
color: #212529;
&::placeholder {
color: #3c4d5a;
}
&:focus {
outline: 2px solid #003da6;
outline-offset: -1px;
}
}
&__search-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
height: 32px;
padding: 5px 9px;
border: 1px solid #003da6;
border-radius: 3.2px;
background: #fff;
color: #003da6;
font-size: 14px;
line-height: 1.5;
white-space: nowrap;
cursor: pointer;
&:hover:not(:disabled) {
background-color: #ccd8ed;
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
&:focus-visible {
outline: 2px solid #003da6;
outline-offset: 2px;
}
}
// Helper hint shown beneath the search field while the Search button is
// disabled, so the disabled state has a clear, visible reason.
&__search-hint {
margin: 4px 0 0;
font-size: 12px;
line-height: 1.5;
color: #3c4d5a;
}
&__search-options-link {
display: inline-block;
margin-top: 4px;
padding: 0;
background: none;
border: none;
color: #003da6;
font-size: 14px;
text-decoration: underline;
cursor: pointer;
&:hover {
color: #003da6;
}
&:focus-visible {
outline: 2px solid #003da6;
outline-offset: 2px;
}
}
&__search-options {
margin-top: 8px;
padding: 0 24px;
}
// Results region — the only flexible region, so it absorbs any overflow
// while the header, search, and footer stay fully visible. The scroll
// itself lives on __results-scroll so the "Also available" heading
// stays pinned above the list.
// No min-height: 0 here — the region must not shrink below its
// min-content (heading + panel floor), or small screens crush the
// list to nothing; __body scrolls instead when space runs out.
&__results {
flex: 1 1 auto;
display: flex;
flex-direction: column;
}
&__results-header {
flex-shrink: 0;
padding: 16px 24px 8px;
font-size: 16px;
color: #212529;
}
// Recessed, tinted panel that owns the scroll. The visible boundary
// (tint + dividers) makes a partially cut-off last row read as "more
// content below" even when the OS hides the scroll bar (CPANEL-53288).
// The 150px floor keeps ~2.5 rows visible even on cramped viewports,
// preserving the cut-off-row scroll affordance.
&__results-scroll {
flex: 1 1 auto;
min-height: 150px;
overflow-y: auto;
background-color: #f8f9fa;
border-top: 1px solid #e7e7e8;
&:focus-visible {
outline: 2px solid #003da6;
outline-offset: -2px;
}
}
&__results-loading {
padding: 16px 24px;
font-size: 14px;
color: #3c4d5a;
text-align: center;
}
&__table {
display: flex;
flex-direction: column;
}
// Use symmetric vertical padding (not a fixed height) so every domain
// row has consistent spacing around its name regardless of content.
// Rows render white on the tinted panel so the panel reads as one
// container; the old alternating stripes fought the panel background.
&__row {
display: flex;
align-items: center;
min-height: 60px;
padding: 12px 24px;
border-bottom: 1px solid #e7e7e8;
background-color: #fff;
}
&__row-domain {
flex: 1;
display: flex;
align-items: center;
gap: 10px;
font-size: 16px;
color: #212529;
min-width: 0;
}
&__premium-tag {
display: inline-block;
padding: 2px 8px;
background-color: #ccd8ed;
border-radius: 3px;
font-size: 12px;
color: #003da6;
white-space: nowrap;
}
&__row-price {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 8px;
min-width: 200px;
text-align: right;
font-size: 14px;
color: #212529;
padding: 0 16px;
white-space: nowrap;
}
// No results
&__no-results {
flex-shrink: 0;
padding: 24px;
text-align: center;
color: #3c4d5a;
font-size: 14px;
}
// Footer — tinted and divided from the list above so the Go Back
// button reads as an anchored action bar instead of floating in
// white space (CPANEL-53288). Sticky so it stays pinned even when
// the body itself scrolls on small viewports; inert when it doesn't.
&__footer {
position: sticky;
bottom: 0;
z-index: 1;
flex-shrink: 0;
padding: 12px 24px;
background-color: #f8f9fa;
border-top: 1px solid #e7e7e8;
}
&__go-back {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 0;
font-size: 14px;
color: #003da6;
text-decoration: none;
cursor: pointer;
&:hover {
color: #003da6;
}
&:focus-visible {
outline: 2px solid #003da6;
outline-offset: 2px;
}
}
// Scrim overlay that hosts the purchase-confirmation and
// redirection-failed cards. It is `position: fixed` so it fills the whole
// viewport and centres the card against it, rather than being clipped to
// this modal's own `max-height: 80vh` box — on short viewports an
// `absolute` overlay squeezed the taller cards into a nested scroll area
// inside the parent modal (CPANEL-55129). Escaping to the viewport also
// requires resetting the identity transform Bootstrap leaves on the shown
// dialog; see the `.domain-search-modal-dialog` block below.
// The card is centred with `margin: auto` on the flex item below, with
// `align-items: flex-start` (not `center`) here: when the card is taller
// than the overlay (short viewport heights or high zoom), auto margins
// collapse to zero and the card pins to the top, so `overflow-y: auto`
// can scroll to its header. `align-items: center` would instead clip the
// top unreachably, hiding the close button (WCAG 1.4.10 reflow).
// `scrollbar-gutter: stable both-edges` reserves the scrollbar's space
// whether or not the overlay is currently scrolling, so the centred card
// keeps the same width across every overlay state (initial confirm,
// popup-blocked, and redirect success). Without it, only the taller
// popup-blocked card overflows and gains a scrollbar, which — on
// platforms with classic (space-consuming) scrollbars — narrows and
// shifts that one state relative to the others. `both-edges` reserves a
// matching gutter on the opposite edge too, so this flex-centring
// container stays symmetric and the card remains geometrically centred
// rather than offset by half a scrollbar width.
&__overlay {
position: fixed;
inset: 0;
z-index: 10;
display: flex;
align-items: flex-start;
justify-content: center;
padding: 20px;
overflow-y: auto;
scrollbar-gutter: stable both-edges;
background: rgba(33, 37, 41, 0.4);
}
// Purchase confirmation / redirection-failed card. Rendered as a compact
// dialog-shaped surface with its own header / body / footer. `margin:
// auto` centres it within the scrim on both axes while keeping the top
// reachable when it overflows — see the __overlay note above.
&__confirmation {
display: flex;
flex-direction: column;
// `margin: auto` centres the card within the flex overlay when there
// is room and collapses to keep the top reachable when it overflows
// (see the &__overlay note above).
margin: auto;
width: 400px;
max-width: 100%;
background: #fff;
border-radius: 4px;
box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1);
outline: none;
}
// Post-checkout "Let's connect your domain!" success state. Rendered
// wider than the 400px "Redirecting to Store" confirmation it replaces
// so the size shift itself signals that the modal has changed state and
// the manual add-domain step is now required (CPANEL-54654).
&__confirmation--wide {
width: 480px;
}
&__confirmation-header {
display: flex;
align-items: center;
gap: 8px;
height: 64px;
padding: 10px 10px 10px 20px;
border-bottom: 1px solid #e7e7e8;
}
// Holds the redirect status SVG (green arrow for "Redirecting to Store",
// green check for "Redirected to Store", red warning for "Redirection
// Failed"). The colour is baked into each SVG asset (Figma nodes
// 76:31088 and 86:10861), so there is no colour rule here.
&__confirmation-icon {
flex-shrink: 0;
display: inline-flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
}
&__confirmation-icon-img {
display: block;
width: 40px;
height: 40px;
}
&__confirmation-title {
flex: 1;
min-width: 0;
margin: 0;
font-family: Lato, sans-serif;
font-size: 20px;
font-weight: 500;
line-height: 1.2;
color: #212529;
word-break: break-word;
outline: none;
}
&__confirmation-close {
flex-shrink: 0;
font-size: 24px;
}
&__confirmation-body {
display: flex;
flex-direction: column;
gap: 30px;
padding: 20px;
}
&__confirmation-intro {
margin: 0;
font-family: Lato, sans-serif;
font-size: 16px;
font-weight: 400;
line-height: 1.5;
color: #212529;
word-break: break-word;
}
// Domain pill — bold, centred, light-blue background.
&__confirmation-domain {
display: flex;
align-items: center;
justify-content: center;
padding: 10px 0;
background-color: rgba(204, 216, 237, 0.5);
border-radius: 8px;
font-family: Lato, sans-serif;
font-size: 16px;
font-weight: 700;
line-height: 1.5;
color: #212529;
word-break: break-word;
text-align: center;
}
&__confirmation-note {
margin: 0;
font-family: Lato, sans-serif;
font-size: 12px;
font-weight: 400;
line-height: 1.5;
color: #212529;
}
// Popup-blocked warning banner (CPANEL-54653). Uses the standard
// Bootstrap warning palette (same colours as this theme's existing
// .alert-warning usage) so the treatment is unmistakably a warning
// and stays within the established design system rather than
// introducing a one-off colour. @state-warning-text on
// @state-warning-bg and on #fff both clear 4.5:1 contrast.
&__popup-blocked {
display: flex;
align-items: flex-start;
gap: 12px;
padding: 12px 16px;
background-color: @state-warning-bg;
border: 1px solid @state-warning-border;
border-left: 4px solid @brand-warning;
border-radius: 4px;
}
&__popup-blocked-icon {
flex-shrink: 0;
margin-top: 2px;
color: @brand-warning;
font-size: 18px;
}
&__popup-blocked-body {
flex: 1 1 auto;
min-width: 0;
}
// Warning title, rendered as an <h3> so the heading relationship is
// exposed to assistive technology while preserving the dialog's
// h2-rooted hierarchy (h2 title -> h3 warning). Explicit font-size and
// margins override the default <h3> styling to match the mockup.
&__popup-blocked-title {
margin: 0 0 4px;
font-family: Lato, sans-serif;
font-size: 15px;
font-weight: 700;
line-height: 1.4;
color: @state-warning-text;
}
// Explanatory body copy beneath the heading. @state-warning-text on
// @state-warning-bg clears 4.5:1 contrast.
&__popup-blocked-message {
margin: 0 0 12px;
font-family: Lato, sans-serif;
font-size: 14px;
font-weight: 400;
line-height: 1.4;
color: @state-warning-text;
}
// Primary call-to-action, using the same solid Figma blue (#003da6) as
// this overlay's "Continue" button rather than the raw Bootstrap
// .btn-primary (#428bca), whose white text only reaches ~3.6:1 and
// fails WCAG AA. White on #003da6 clears AA at ~9.5:1, matches the
// approved mockup, and replaces the earlier plain text link.
&__popup-blocked-link.btn {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 7px 13px;
background-color: #003da6;
border: 1px solid #003da6;
border-radius: 4px;
color: #fff;
white-space: nowrap;
text-decoration: none;
&:hover:not(:disabled),
&:focus:not(:disabled) {
background-color: #002f80;
border-color: #002f80;
color: #fff;
text-decoration: none;
}
&:focus-visible {
outline: 2px solid #003da6;
outline-offset: 2px;
}
}
// Prominent "next step" callout in the "Let's connect your domain!"
// success state. Replaces the small side-note styling so the manual
// add-domain step is unmistakable on first view (CPANEL-54654). The
// left accent, tinted background, and uppercase label read as an
// action-required notice without relying on colour alone.
&__next-step {
display: flex;
align-items: flex-start;
gap: 12px;
padding: 16px;
background-color: #eaf1fb;
border: 1px solid #b6ccef;
border-left: 4px solid #003da6;
border-radius: 4px;
}
&__next-step-icon {
flex-shrink: 0;
margin-top: 2px;
font-size: 20px;
line-height: 1.4;
color: #003da6;
}
&__next-step-content {
display: flex;
flex-direction: column;
gap: 4px;
min-width: 0;
}
&__next-step-label {
margin: 0;
font-family: Lato, sans-serif;
font-size: 12px;
font-weight: 700;
line-height: 1.2;
letter-spacing: 0.06em;
text-transform: uppercase;
color: #003da6;
}
&__next-step-text {
margin: 0;
font-family: Lato, sans-serif;
font-size: 16px;
font-weight: 600;
line-height: 1.5;
color: #212529;
word-break: break-word;
}
&__confirmation-actions {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
height: 64px;
padding: 0 20px;
// Within the confirmation footer the Go Back button matches Figma
// node 76:31126 (16px text, 6px 12px padding) rather than the
// smaller default used elsewhere.
.domain-search-modal__go-back {
padding: 6px 12px;
font-size: 16px;
}
// The "Redirection Failed" footer holds only the Continue button,
// right-aligned (Figma node 86:10861).
&--end {
justify-content: flex-end;
}
}
// Primary "Continue" button — solid blue per Figma.
&__confirmation-continue.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 7px 13px;
background-color: #003da6;
border: 1px solid #003da6;
border-radius: 4px;
color: #fff;
font-family: Lato, sans-serif;
font-size: 16px;
line-height: 1.5;
white-space: nowrap;
&:hover:not(:disabled),
&:focus:not(:disabled) {
background-color: #002f80;
border-color: #002f80;
color: #fff;
}
&:focus-visible {
outline: 2px solid #003da6;
outline-offset: 2px;
}
&:disabled {
opacity: 0.65;
cursor: not-allowed;
}
}
}
// Modal backdrop & container overrides for UI-Bootstrap
.domain-search-modal-dialog {
.modal-dialog {
width: 800px;
max-width: 95vw;
margin: 80px auto;
}
// Bootstrap animates the dialog in by settling it on an identity
// `transform: translate(0, 0)` in the shown (`.in`) state. A non-`none`
// transform makes the element the containing block for its
// `position: fixed` descendants, which would pin the redirect overlay to
// this dialog instead of the viewport and reintroduce the clipping this
// fix removes (CPANEL-55129). Reset it once shown so the overlay resolves
// against the viewport; the fade-in is unaffected because it interpolates
// to the identity transform regardless. Scoped to this window class (with
// `.modal` for specificity over Bootstrap's `.modal.in .modal-dialog`) so
// no other modal's animation changes.
&.modal.in .modal-dialog {
-webkit-transform: none;
transform: none;
}
.modal-content {
padding: 0;
border: none;
border-radius: 8px;
background: transparent;
box-shadow: 0 8px 20px 0 rgba(2, 2, 2, 0.04);
overflow: hidden;
}
}
// Responsive: stack featured cards on narrow viewports
@media (max-width: 767px) {
.domain-search-modal {
&__title {
font-size: 24px;
}
&__featured {
flex-direction: column;
}
&__search-row {
flex-direction: column;
}
&__search-btn {
width: 100%;
}
&__row {
flex-wrap: wrap;
height: auto;
padding: 12px 24px;
gap: 8px;
}
&__row-price {
width: auto;
text-align: left;
padding: 0;
}
&__success {
padding-right: 24px;
padding-bottom: 130px;
}
&__success-title {
font-size: 24px;
word-break: break-word;
}
&__success-graphic {
width: 110px;
height: 110px;
}
&__unavailable-title {
font-size: 24px;
}
}
}
@media (prefers-reduced-motion: reduce) {
.domain-search-modal {
&__skeleton-block {
animation: none;
opacity: 0.2;
}
}
}
Back to Directory