/* ---------------------------------------------------------------------------
   ICONS: Phosphor Thin (MIT), not Tabler. Markup is `<i class="ph-thin ph-x">`.

   Why we left Tabler (2026-08-13): the icons read far too heavy, and Tabler's
   own webfont could not get light enough. Measured median ink area per glyph
   (fontTools AreaPen, normalised to em²):

     Tabler stroke 2.0 (theme default)  0.30534
     Tabler stroke 1.0 (its lightest)   0.13676
     Themify                            0.17313   heavier, and no tiktok /
                                                  snapchat / whatsapp / X /
                                                  google-play glyphs at all
     FontAwesome 6 Free Solid           0.53718   ~4x heavier; its Light and
                                                  Thin styles are Pro-only
     Phosphor Thin                      0.08289   <- in use, 39% under Tabler's
                                                  lightest, all brands present

   Two things were making icons bold, and the font was the smaller one. The
   theme's own `.ti` rule sets `-webkit-text-stroke-width: .5px`, painting a
   half-pixel outline around every glyph ON TOP of its stroke — on a 1px glyph
   that is close to double weight. `.ph-thin` is not matched by any theme rule,
   so Phosphor escapes it; the guard below keeps it that way if the theme is
   ever updated to target icons more broadly.

   Do NOT reintroduce `.ti` markup: the theme's inlined copy of Tabler still
   defines those classes, so a stray `.ti` renders — just bold and in the wrong
   family, which is exactly the bug this block replaced.

   All five weights ship: `ph` (regular), `ph-thin`, `ph-light`, `ph-bold`,
   `ph-fill`. The guard below covers every one, so choosing a heavier weight is
   a deliberate decision rather than something the theme does behind our backs.
   --------------------------------------------------------------------------- */
.ph,
.ph-thin,
.ph-light,
.ph-bold,
.ph-fill {
  /* zero the theme's 0.5px glyph outline (see above) and block faux-bold
     synthesis from a bold parent — both silently thicken every icon. */
  -webkit-text-stroke-width: 0;
  font-weight: 400 !important;
}

/* The canvas has to start at the document root, not at .page-wrapper. iOS
   Safari paints html's background in the rubber-band overscroll area and behind
   the status bar, so with html left at its default the site showed a white strip
   above the header and below the footer. color-scheme tells Safari to render
   scrollbars and form controls dark to match. */
html,
body {
  background-color: #000000;
}

html {
  color-scheme: dark;
}

/* Black canvas. !important because the markup still carries Bootstrap's
   background utility on this element, and that utility is itself !important. */
.page-wrapper {
  background:
    radial-gradient(90% 60% at 15% -8%, rgba(var(--brand-rgb), 0.10), transparent 55%),
    #000000 !important;
}

.header {
  position: fixed;
  inset: 0 0 auto 0;
  width: 100%;
  z-index: 1100;
  background: transparent;
  border-bottom-color: transparent !important;
  box-shadow: none !important;
  transition: background-color 0.25s ease, border-color 0.25s ease, box-shadow 0.25s ease, backdrop-filter 0.25s ease;
}

.header.is-scrolled {
  background: rgba(255, 255, 255, 0.94);
  border-bottom-color: rgba(15, 23, 42, 0.08) !important;
  box-shadow: 0 12px 32px rgba(15, 23, 42, 0.08) !important;
  backdrop-filter: blur(14px);
}

/* The fixed header used to be cleared with padding here, which left a strip
   above the hero showing only the bare page background. Because the hero has
   overflow:hidden (for the icon rain), its glow layer is clipped to its own
   box and can't reach into that strip — so it rendered as a darker band
   across the top. The hero now starts at y=0 and reserves the header height
   in its own padding instead: background runs edge to edge, content clears
   the header. */
.body-wrapper {
  padding-top: 0;
}

/* --- overflow-hidden vs. position: sticky ---------------------------------
   Both wrappers carry Bootstrap's `overflow-hidden` in the layout markup, to
   stop wide artwork causing a horizontal scrollbar. But a non-visible
   `overflow` on ANY ancestor scopes `position: sticky` to that ancestor's box —
   and these two are as tall as the document, so a sticky child has nowhere to
   stick and silently behaves as static.

   That is what broke the homepage showcase: its stage scrolled straight off the
   top (measured: top 0 -> -1800 across the section) leaving two viewports of
   empty black, while the JS carried on swapping panels nobody could see.

   `overflow-x: clip` clips exactly like `hidden` but does NOT create a scroll
   container, so sticky keeps working. It has to be paired with an explicit
   `overflow-y: visible`: with `hidden`/`visible` the pair is illegal and the
   browser silently promotes the visible axis to `auto`, which reintroduces the
   scroll container this is trying to avoid. `clip` has no such rule.
   `!important` because the utility class it overrides is itself important.

   An older browser that does not know `clip` drops these declarations and
   falls back to the theme's `hidden` — the showcase stops sticking, nothing
   else breaks. */
.body-wrapper,
.page-wrapper {
  overflow-x: clip !important;
  overflow-y: visible !important;
}

:root {
  --romman-header-h: 88px;
}

.header .navbar {
  min-height: 88px;
}

.header .navbar-brand,
.header .nav-link,
.header .navbar-toggler {
  color: #111827;
}

.header .navbar-nav .nav-item .nav-link {
  color: rgba(17, 24, 39, 0.78);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  font-size: 0.8rem;
}

.header .navbar-nav .nav-item .nav-link:hover,
.header .navbar-nav .nav-item .nav-link.active {
  color: #111827;
  background: transparent;
}

.romman-home .header {
  border-bottom: 1px solid rgba(15, 23, 42, 0.08) !important;
}

.romman-home .header:not(.is-scrolled) {
  background: transparent;
  border-bottom-color: transparent !important;
}


.romman-home .header:not(.is-scrolled) .navbar-brand,
.romman-home .header:not(.is-scrolled) .nav-link,
.romman-home .header:not(.is-scrolled) .navbar-toggler,
.romman-home .header:not(.is-scrolled) .navbar-nav .nav-item .nav-link,
.romman-home .header:not(.is-scrolled) .romman-locale-toggle {
  color: #ffffff;
}

.romman-home .header:not(.is-scrolled) .navbar-nav .nav-item .nav-link:hover,
.romman-home .header:not(.is-scrolled) .navbar-nav .nav-item .nav-link.active {
  color: #ffffff;
}

.romman-brand-logo {
  height: 42px;
  width: auto;
}

.romman-locale-toggle {
  width: 46px;
  height: 46px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 999px;
  border: 1px solid rgba(15, 23, 42, 0.14);
  background: transparent;
  color: black;
  box-shadow: none;
}

.romman-locale-toggle::after {
  display: none;
}

.romman-locale-toggle:hover,
.romman-locale-toggle:focus,
.romman-locale-toggle.show {
  background: rgba(15, 23, 42, 0.06);
  border-color: rgba(15, 23, 42, 0.22);
  color: black;
}

.romman-home .header:not(.is-scrolled) .romman-locale-toggle {
  border-color: rgba(255, 255, 255, 0.26);
}

.romman-home .header:not(.is-scrolled) .romman-locale-toggle:hover,
.romman-home .header:not(.is-scrolled) .romman-locale-toggle:focus,
.romman-home .header:not(.is-scrolled) .romman-locale-toggle.show {
  background: rgba(255, 255, 255, 0.1);
  border-color: rgba(255, 255, 255, 0.42);
}

.romman-locale-menu {
  min-width: 11rem;
  padding: 0.5rem;
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: 16px;
  /* Same material as the country picker panel: this is the site's other floating
     dropdown, and the two should read as the same component. Near-opaque because it
     sits over arbitrary page content. */
  background: rgba(18, 19, 24, 0.94);
  box-shadow: 0 22px 52px rgba(0, 0, 0, 0.50);
}

.romman-locale-menu[data-bs-popper] {
  top: calc(100% + 0.6rem);
}

.romman-locale-menu .dropdown-item {
  border-radius: 12px;
  padding: 0.7rem 0.9rem;
  color: rgba(253, 244, 245, 0.88);
  font-weight: 600;
}

.romman-locale-menu .dropdown-item:hover,
.romman-locale-menu .dropdown-item:focus,
.romman-locale-menu .dropdown-item.active {
  background: rgba(255, 255, 255, 0.12);
  color: #ffffff;
}

/* The language control inside the drawer. These were dark-on-white to suit the
   old white pane; the drawer is dark glass now, so they invert with it. */
.offcanvas .romman-locale-toggle {
  border-color: rgba(255, 255, 255, 0.28);
  color: #fdf4f5;
}

.offcanvas .romman-locale-toggle:hover,
.offcanvas .romman-locale-toggle:focus,
.offcanvas .romman-locale-toggle.show {
  background: rgba(255, 255, 255, 0.14);
  border-color: rgba(255, 255, 255, 0.42);
  color: #ffffff;
}

/* The drawer's copy of this menu had its own dark fill and a white hover. Both are
   gone: the one definition above now covers the header and the drawer, so the two
   cannot drift to slightly different darks or different hover colours again. */

html[dir="ltr"] .romman-locale-menu[data-bs-popper] {
  right: 0;
  left: auto;
}

html[dir="rtl"] .romman-locale-menu[data-bs-popper] {
  right: auto;
  left: 0;
}

.romman-riot-hero {
  min-height: 100vh;
  /* Transparent, NOT #000: the page's fixed live backdrop is a preceding
     sibling, so an opaque hero would paint straight over its crimson blooms
     and the copy side would be a flat black rectangle. (This was `background:
     #000` while a video filled the section.) */
  background: transparent;
}

/* ┌── HERO LAYER ORDER ─────────────────────────────────────────────────┐
   │ 0  .romman-hero-globe      the rotating icon sphere                │
   │ 1  .romman-riot-hero-overlay  scrim — over the globe, under copy    │
   │ 2  .romman-riot-hero .row  the headline and buttons                 │
   │                                                                     │
   │ These must be explicit. Source order alone would paint the scrim    │
   │ UNDER the globe (it comes first in the markup), so the copy side    │
   │ would never darken and the headline would sit on raw artwork.       │
   │                                                                     │
   │ The row carries layer 2 rather than the container, because the globe │
   │ is a container child now: absolutely positioned, it would otherwise  │
   │ paint over the row's in-flow copy regardless of markup order. The    │
   │ two halves do not overlap, so nothing depends on this today — it is  │
   │ here so a wider sphere or a bled halo cannot quietly cover the       │
   │ headline later.                                                      │
   └─────────────────────────────────────────────────────────────────────┘ */
.romman-riot-hero .row {
  position: relative;
  z-index: 2;
}

.romman-riot-hero-overlay {
  position: absolute;
  inset: 0;
  z-index: 1;
  /* Heavier on the copy side than it was: it is now dimming real artwork
     rather than empty black, and the headline still has to win. */
  /* background:
    linear-gradient(90deg, rgba(0, 0, 0, 0.94) 0%, rgba(0, 0, 0, 0.86) 30%, rgba(0, 0, 0, 0.52) 55%, rgba(0, 0, 0, 0.3) 78%, rgba(0, 0, 0, 0.42) 100%),
    linear-gradient(180deg, rgba(0, 0, 0, 0.35) 0%, transparent 30%, rgba(0, 0, 0, 0.55) 100%); */
}

.romman-riot-hero > .container {
  z-index: 2;
}

.romman-kicker,
.romman-kicker-sm {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

/* Gradient brand text. Opt in anywhere with .romman-gradient-text; the kicker
   is listed here rather than carrying its own copy of the recipe so there is
   one definition to change.

   Only for SHORT, tracked, uppercase label text. A gradient clipped to text is
   painted across the whole element box, so a long or wrapping string hands its
   first word the rose end and its last word the teal end. Brand-coloured text
   that is a link or a hover state stays flat -- a text-clipped gradient cannot
   interpolate, so the transition would die.

   THE MEMBER LIST IS COMPLETE, and it is short for a reason. Do not go hunting
   through `color: var(--brand)` rules to find more candidates: ~40 rules declare
   it, but a runtime sweep of every rendered element on /, /about, /apps,
   /careers, /contact and /cookies found only THREE kinds actually painting brand
   red at rest -- the kickers, .romman-job-tag (homepage only), and a handful of
   14-15px icon glyphs. /apps, /careers and /cookies had ZERO: on those pages the
   .romman-immersive rules force .romman-app-flag, .romman-app-card-cta,
   .romman-app-card h5 and the tile glyphs to warm-white #fdf4f5, outranking
   their base brand colour. Everything else in those ~40 rules is a hover state,
   a focus state or an error. Re-run the sweep rather than reading the CSS.

   The 14-15px icons are excluded on size: a 15px box gives the ramp ~15px to
   travel, so the glyph lands on one flat mid-ramp hue -- it loses the brand red
   and gains no visible gradient.

   `.romman-value-mark i` is in the list because an ICON GLYPH qualifies on two
   conditions, and it is the only one on the site that meets both: it is large
   (2rem — 32px of ink for the ramp to travel across; it was a 2.2-3.2rem clamp
   while the section was a full-screen reel, and shrank when the points became
   rows) and it sits on the bare page canvas. Measured against the alternatives: put the ramp
   on the whole .romman-value-mark row instead and its 736px box drops the icon
   and the numeral in the periwinkle/cyan middle, so every trace of brand red
   disappears; put it on a glyph sitting INSIDE a brand-tinted tile
   (.romman-about-icon, .romman-file-icon) and the cool half fights the red
   behind it and the icon reads as a sticker. On those tiles the TILE is the
   filled surface, so the tile takes --brand-gradient-tint and the glyph stays
   the solid warm-white mark it already was.

   `color` is the fallback, not decoration: without background-clip the
   transparent text-fill renders the label invisible, so the clip and the fill
   are declared together inside @supports and never apart. */
.romman-kicker,
.romman-value-mark i,
.romman-job-tag,
.romman-gradient-text {
  color: var(--brand);
  /* Load-bearing, not cosmetic. The gradient is painted across the ELEMENT box
     and then clipped to the glyphs, so a block-level label whose box is wider
     than its text samples only the head of the ramp: .romman-job-tag is a plain
     block div, 322px wide around 58px of ink, and the naive version rendered as
     flat washed-out rose -- no gradient visible AND the brand red gone, strictly
     worse than leaving it alone. Hugging the ink maps the whole ramp onto the
     label. It is a no-op for members that already shrink-to-fit (.romman-kicker
     is inline-flex, .romman-value-mark i is a flex item), and measured to change
     nothing about the job tiles' own layout (376x337 before and after, no
     horizontal overflow, start-aligned in RTL). */
  width: fit-content;
}

@supports ((-webkit-background-clip: text) or (background-clip: text)) {
  .romman-kicker,
  .romman-value-mark i,
  .romman-job-tag,
  .romman-gradient-text {
    background-image: var(--brand-gradient-text);
    /* !important because the theme sets background-clip: padding-box on a long
       list of components and any of these can land inside one. */
    -webkit-background-clip: text !important;
    background-clip: text !important;
    -webkit-text-fill-color: transparent;
  }
}

/* Forced-colours mode swaps our palette for the user's own but does not touch
   -webkit-text-fill-color, so without this the label keeps painting OUR gradient
   over the user's forced background -- the one palette they explicitly asked to
   override. CanvasText and not currentColor: Chromium computes currentColor from
   the author `color` BEFORE forced colours substitute theirs, so currentColor
   here lands on brand red (measured) rather than on the forced text colour. */
@media (forced-colors: active) {
  .romman-kicker,
  .romman-value-mark i,
  .romman-job-tag,
  .romman-gradient-text {
    background-image: none;
    -webkit-text-fill-color: CanvasText;
  }

  /* The mark's glow is three brand-coloured drop-shadows. Forced colours does
     not touch `filter` any more than it touches -webkit-text-fill-color, so
     without this the glyph adopts the user's palette and then keeps a crimson
     halo around it — our colour surviving in the one mode that exists to
     replace it. */
  .romman-value-mark i {
    filter: none;
  }
}

.romman-kicker-sm {
  color: #64748b;
}

.romman-riot-title {
  max-width: 780px;
  color: #ffffff;
  font-size: clamp(3rem, 6vw, 6rem);
  font-weight: 800;
  line-height: 0.94;
  letter-spacing: -0.04em;
  text-transform: uppercase;
}

.romman-riot-copy {
  max-width: 760px;
  color: rgba(255, 255, 255, 0.86);
  font-size: 1.05rem;
  line-height: 1.75;
}

.romman-section-copy {
  max-width: 760px;
  color: #556070;
  font-size: 1.05rem;
  line-height: 1.75;
}

.romman-section-copy.dark {
  color: #556070;
}

.romman-btn-solid,
.romman-btn-ghost,
.romman-btn-dark,
.romman-btn-outline-dark {
  min-width: 180px;
  padding: 0.95rem 1.35rem;
  border-radius: 0;
  font-weight: 700;
  text-transform: uppercase;
}

.romman-btn-solid {
  /* Colour under image, so a dropped gradient still leaves a brand-red button
     rather than a transparent one -- .btn pulls background-color from
     Bootstrap's custom properties and this markup carries no .btn-* variant to
     fill them in. */
  background-color: var(--brand);
  background-image: var(--brand-gradient);
  /* transparent, not removed: the shared white hover further down paints
     border-color #111111, so the 1px has to stay reserved or the button resizes
     on hover. A flat brand-red border was also lighter than the wine end of the
     fill, which drew a bright ring around the button's darkest corner. */
  border: 1px solid transparent;
  /* Top catch-light from the glass system -- what makes a gradient fill read as
     a lit surface instead of a printed swatch. */
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.16);
  color: #fff;
}

.romman-btn-ghost {
  background: rgba(255, 255, 255, 0.12);
  border: 1px solid rgba(255, 255, 255, 0.34);
  color: #fff;
}

.romman-btn-dark {
  background: #111111;
  border: 1px solid #111111;
  color: #fff;
}

.romman-btn-outline-dark {
  background: transparent;
  border: 1px solid rgba(17, 17, 17, 0.22);
  color: #111111;
}

.romman-hero-stats .romman-stat-block{
  height: 100%;
  background: rgba(255, 255, 255, 0.16);
  border: 1px solid rgba(255, 255, 255, 0.18);
  padding: 1.15rem 1.2rem;
  backdrop-filter: blur(12px);
}

.romman-stat-block strong{
  display: block;
  color: #ffffff;
  font-size: 2rem;
  font-weight: 800;
}

.romman-stat-block span{
  color: rgba(255, 255, 255, 0.86);
  font-size: 0.9rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.romman-dark-section {
  background: transparent;
}

.romman-light-section {
   background:
    linear-gradient(135deg, rgba(255, 255, 255, 0.94), rgba(239, 242, 247, 0.98)),
    url("../images/backgrounds/amman_dubai.jpg") center/cover no-repeat;
}

.romman-section-title {
  font-size: clamp(2rem, 3vw, 3.2rem);
  font-weight: 800;
  line-height: 1.04;
  text-transform: uppercase;
  letter-spacing: -0.03em;
}

.romman-feature-story {
  display: grid;
  grid-template-columns: minmax(220px, 280px) 1fr;
  min-height: 100%;
  background: #ffffff;
  color: inherit;
  box-shadow: 0 20px 50px rgba(15, 23, 42, 0.08);
}

.romman-feature-story-image {
  display: grid;
  place-items: center;
  background:
    radial-gradient(circle at top, rgba(var(--brand-rgb), 0.28), transparent 50%),
    linear-gradient(135deg, #f7e8ea 0%, #f1f5f9 100%);
}

.romman-feature-story-image img {
  width: 150px;
  height: 150px;
  object-fit: cover;
  border-radius: 32px;
}

.romman-feature-story-body {
  padding: 2rem;
}

.romman-feature-story-body span,
.romman-feed-item span,
.romman-story-panel span {
  color: #64748b;
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.romman-feature-story-body h3,
.romman-story-panel h3 {
  color: #111827;
  font-size: 1.6rem;
  font-weight: 800;
  line-height: 1.1;
}

.romman-feature-story-body p,
.romman-story-panel p {
  color: #556070;
  line-height: 1.75;
}

.romman-feed-list {
  background: #ffffff;
  box-shadow: 0 20px 50px rgba(15, 23, 42, 0.08);
}

.romman-feed-item {
  display: block;
  padding: 1.5rem;
  color: inherit;
}

.romman-feed-item + .romman-feed-item {
  border-top: 1px solid rgba(15, 23, 42, 0.08);
}

.romman-feed-item strong {
  display: block;
  margin-top: 0.35rem;
  color: #111827;
  font-size: 1.1rem;
}

.romman-feed-item p {
  margin: 0.75rem 0 0;
  color: #64748b;
  line-height: 1.65;
}

.romman-game-card {
  display: block;
  height: 100%;
  background: #ffffff;
  color: inherit;
  box-shadow: 0 24px 60px rgba(16, 24, 40, 0.08);
  transition: transform 0.25s ease, box-shadow 0.25s ease;
}

.romman-game-card:hover {
  transform: translateY(-6px);
}

.romman-app-carousel .owl-stage {
  display: flex;
}

.romman-app-carousel-shell {
  width: 100vw;
  margin-left: calc(50% - 50vw);
  margin-right: calc(50% - 50vw);
  overflow: visible;
  background: transparent;
  padding: 1.75rem 0 2.25rem;
}

.romman-app-carousel {
  padding: 0 1.5rem;
  background: transparent;
}

.romman-app-carousel .owl-stage-outer,
.romman-app-carousel .owl-item {
  overflow: visible;
}

.romman-app-carousel .owl-item {
  height: auto;
  opacity: 1;
  transition: opacity 0.35s ease;
  display: flex;
  justify-content: center;
}

.romman-app-carousel .owl-item.active.center {
  opacity: 1;
}

.romman-app-carousel-item,
.romman-app-carousel-item .romman-game-card {
  height: 100%;
}

.romman-app-carousel-item {
  width: 100%;
  display: flex;
  justify-content: center;
}

.romman-app-carousel .owl-item .romman-game-card--carousel {
  transform: none;
}

.romman-app-carousel .owl-item .romman-game-card--carousel:hover {
  transform: translateY(-6px);
}

.romman-game-card-art {
  min-height: 260px;
  display: grid;
  place-items: center;
  background: linear-gradient(135deg, #f6f7fb 0%, #f2dfe1 55%, #e3edf7 100%);
}

.romman-game-card-art img {
  width: 140px;
  height: 140px;
  object-fit: cover;
  border-radius: 30px;
  box-shadow: 0 18px 45px rgba(0, 0, 0, 0.18);
}

.romman-game-card-body {
  padding: 1.5rem;
}

.romman-game-card-body h3 {
  color: #111827;
}

.romman-game-card-body p {
  margin: 0.9rem 0 0;
  color: #64748b;
  line-height: 1.7;
}

.romman-game-card--carousel {
  position: relative;
  width: min(100%, 360px);
  aspect-ratio: 0.66;
  min-height: 0;
  overflow: visible;
  margin: 0 auto;
  background: transparent;
  box-shadow: none;
}

.romman-game-card--carousel .romman-game-card-backdrop {
  position: absolute;
  inset: 0;
  overflow: hidden;
  clip-path: polygon(10% 0, 100% 0, 90% 100%, 0 100%);
  background: var(--romman-app-art) center center/cover no-repeat;
  transform: scale(0.94);
  transform-origin: center center;
  box-shadow: 0 24px 60px rgba(16, 24, 40, 0.12);
  transition: transform 0.35s ease, box-shadow 0.35s ease;
}

.romman-app-carousel .owl-item.active.center .romman-game-card--carousel .romman-game-card-backdrop {
  transform: scale(1);
  box-shadow: 0 32px 80px rgba(16, 24, 40, 0.18);
}

.romman-game-card--carousel .romman-game-card-body {
  position: relative;
  z-index: 1;
  height: 100%;
  padding: 2rem 1.6rem 1.5rem;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: 1.2rem;
}

.romman-game-card-copy {
  max-width: 90%;
}

.romman-game-card--carousel h3 {
  margin: 0;
  color: #ffffff;
  font-size: 1.5rem;
  font-weight: 800;
  line-height: 1.05;
  text-transform: uppercase;
  text-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
}

.romman-game-card--carousel p {
  margin: 0.8rem 0 0;
  color: rgba(255, 255, 255, 0.92);
  line-height: 1.75;
  text-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
}

.romman-game-card-platforms {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.romman-platform-chip {
  width: 48px;
  height: 48px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, 0.16);
  background: rgba(255, 255, 255, 0.12);
  color: #ffffff;
  backdrop-filter: blur(10px);
  font-size: 1.15rem;
}

.romman-network-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1rem;
}

.romman-network-card,
.romman-story-panel{
  display: block;
  background: #ffffff;
  padding: 1.4rem;
  color: inherit;
  box-shadow: 0 20px 50px rgba(15, 23, 42, 0.08);
}

.romman-network-card span {
  display: block;
  color: #64748b;
  font-size: 0.9rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
}

.romman-network-card strong {
  display: block;
  margin-top: 0.75rem;
  color: #111827;
  font-size: 1.7rem;
  font-weight: 800;
}

.romman-story-stack {
  display: grid;
  gap: 1rem;
}

.romman-story-mini {
  display: grid;
  grid-template-columns: 56px 1fr;
  gap: 1rem;
  align-items: center;
  background: #ffffff;
  padding: 1.1rem;
  box-shadow: 0 20px 50px rgba(15, 23, 42, 0.08);
}

.romman-story-mini + .romman-story-mini{
  margin-top: 1rem;
}

.romman-story-mini img {
  width: 56px;
  height: 56px;
  object-fit: cover;
  border-radius: 14px;
}

.romman-story-mini strong {
  display: block;
  color: #111827;
  font-size: 0.95rem;
}

.romman-story-mini span {
  display: block;
  color: #64748b;
  font-size: 0.82rem;
  line-height: 1.5;
}

.romman-inline-link {
  color: #111111;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.romman-inline-link.light {
  color: #111111;
}

/* The footer is a floating glass panel, same material as the header pill:
   the bar itself carries nothing, the container is the pane. */
.footer-part {
  background: transparent;
  border-top: 0;
  padding-bottom: 1.75rem;
}

.romman-footer-panel {
  background: rgba(255, 255, 255, 0.09);
  border: 1px solid rgba(255, 255, 255, 0.09);
  border-radius: 26px;
  box-shadow: none;
  -webkit-backdrop-filter: blur(var(--glass-blur, 20px)) saturate(160%);
  backdrop-filter: blur(var(--glass-blur, 20px)) saturate(160%);
  padding-inline: 1.4rem;
  transition: background-color 0.3s var(--romman-ease), box-shadow 0.3s var(--romman-ease);
}

.footer-part .social-btn {
  border: 1px solid rgba(15, 23, 42, 0.12);
  background: transparent;
  color: #111827;
}

.romman-social-icon {
  width: 42px;
  height: 42px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 999px;
  border: 1px solid rgba(15, 23, 42, 0.12);
  color: #111827;
  background: #ffffff;
  transition: transform 0.2s ease, border-color 0.2s ease, color 0.2s ease, box-shadow 0.2s ease;
}

.romman-social-icon:hover {
  transform: translateY(-2px);
  border-color: rgba(var(--brand-blend-rgb), 0.25);
  color: var(--brand);
  box-shadow: 0 10px 24px rgba(15, 23, 42, 0.08);
}

.social-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.65rem 1rem;
  border-radius: 999px;
  font-weight: 600;
}

.romman-footer-note {
  max-width: 560px;
  color: #64748b;
  line-height: 1.7;
}

.romman-footer-icon {
  width: 64px;
  height: 64px;
  object-fit: contain;
  display: block;
}

html[dir="rtl"] .header .navbar-nav {
  margin-left: 0 !important;
  margin-right: auto !important;
}

/* RTL footer: push the social block to the far (left) edge, mirroring LTR */
html[dir="rtl"] .footer-part .text-lg-end {
  text-align: left !important;
}

/* Arabic typography — Tajawal pairs with Plus Jakarta Sans; Latin/numerals
   fall back to the Latin stack. Applied via Bootstrap's font variables so it
   cascades everywhere (body, buttons, inputs …). Icons keep tabler-icons. */
html[lang="ar"] {
  --bs-body-font-family: 'Tajawal', 'Plus Jakarta Sans', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --bs-font-sans-serif: 'Tajawal', 'Plus Jakarta Sans', system-ui, -apple-system, 'Segoe UI', sans-serif;
}

/* Arabic display type needs its own metrics — the Latin ones clip it.
   Keyed on lang, not dir: this is about the SCRIPT being drawn, and it has to
   hold even where a block of Arabic sits inside an LTR page.

   line-height   The Latin titles run at 0.94/1.04, which works for a
                 capital-height, descender-light uppercase setting. Arabic
                 stacks a taller box: ascenders (ا ل ك), deep descenders
                 (ج ح خ ع غ م ن ي ة) and tashkeel above AND below the baseline.
                 At 0.94 the kasra under "أسرعِ" collided with the line beneath
                 it and the tanween on "وضوحًا" overlapped the descenders above.
   letter-spacing  Arabic is a connected script. Negative tracking pulls the
                 joins into each other and frays the connections, so the
                 -0.03/-0.04em Latin tightening is reset rather than softened.
   text-transform  A no-op on a script with no case; dropped so it cannot
                 affect any Latin words or numerals mixed into a heading. */
html[lang="ar"] .romman-riot-title {
  line-height: 1.32;
  letter-spacing: normal;
  text-transform: none;
}

html[lang="ar"] .romman-section-title {
  line-height: 1.3;
  letter-spacing: normal;
  text-transform: none;
}

/* Same treatment for the "How we work" point titles. They sit at 1.1 for Latin
   and do not wrap in Arabic at today's copy — but they moved into third-width
   columns when that section became a grid, so the next title one word longer
   would wrap, and 1.1 is where the clipping starts. */
html[lang="ar"] .romman-value-title {
  line-height: 1.35;
  letter-spacing: normal;
}

html[dir="rtl"] .romman-riot-copy,
html[dir="rtl"] .romman-section-copy,
html[dir="rtl"] .romman-feature-story-body p,
html[dir="rtl"] .romman-story-panel p,
html[dir="rtl"] .romman-feed-item p,
html[dir="rtl"] .romman-game-card-body p {
  max-width: none;
}

@media (max-width: 991.98px) {
  .romman-home .header .navbar-collapse {
    background: #ffffff;
  }

  .romman-network-grid,
  .romman-feature-story {
    grid-template-columns: 1fr;
  }

  .romman-riot-hero {
    min-height: auto;
  }
}

@media (max-width: 767.98px) {
  :root {
    --romman-header-h: 80px;
  }

  .romman-brand-logo {
    height: 36px;
  }

  .romman-riot-title {
    font-size: 2.5rem;
  }

  .romman-feature-story-body,
  .romman-feed-item,
  .romman-game-card-body {
    padding: 1.25rem;
  }

  .romman-game-card--carousel {
    width: min(82vw, 360px);
    aspect-ratio: 0.68;
  }

  .romman-app-carousel .owl-item .romman-game-card--carousel {
    transform: none;
  }

  .romman-app-carousel .owl-item .romman-game-card--carousel:hover {
    transform: translateY(-6px);
  }

  .romman-game-card--carousel .romman-game-card-backdrop {
    clip-path: polygon(7% 0, 100% 0, 93% 100%, 0 100%);
    background: var(--romman-app-art) center center/cover no-repeat;
    transform: none;
    box-shadow: 0 22px 50px rgba(16, 24, 40, 0.12);
  }

  .romman-network-grid {
    grid-template-columns: 1fr;
  }
}

@media (min-width: 1200px) {
  .romman-app-carousel {
    padding: 0 2.5rem;
  }
}

.py-6 {
  padding-top: 4rem !important;
  padding-bottom: 4rem !important;
}

.py-7 {
  padding-top: 8rem !important;
  padding-bottom: 8rem !important;
}

/* ==========================================================================
   iOS 26 "Liquid Glass" system + Motion micro-interactions
   Added on top of the existing brand language (bold type, red accent).
   ========================================================================== */

:root {
  /* Brand accent — change these three to re-tint the whole site */
  --brand: #c93434;
  --brand-strong: #8f163f;
  --brand-rgb: 201, 52, 52;;
  /* 20px + saturate(180%): at 40px the backdrop smeared into a flat wash and
     the glass read as a fill rather than a blur. Less blur keeps detail
     behind the pane recognisable, and the saturation boost lets the backdrop
     colour bleed through — which is what actually sells "glass". */
  --glass-blur: 20px;
  --glass-saturate: 180%;
  /* Rounding — macOS windows/panels sit tighter than a pill */
  --glass-radius: 16px;
  --glass-radius-sm: 12px;
  --glass-radius-pill: 999px;
  /* White glass: flat #ffffff40 fill (the white counterpart of #00000040),
     a 1px ring for the edge, an inset ring, and one soft drop shadow —
     the same layer order as inset-shadow / inset-ring / ring / shadow. */
  --glass-card-bg: rgba(255, 255, 255, 0.75);
  --glass-card-border: rgba(0, 0, 0, 0.08);
  --glass-card-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.12), 0 8px 32px rgba(0, 0, 0, 0.12);
  /* Translucent glass that sits over imagery / video (light content) */
  --glass-over-media-bg: linear-gradient(150deg, rgba(255, 255, 255, 0.24), rgba(255, 255, 255, 0.08));
  --glass-over-media-border: rgba(255, 255, 255, 0.30);
  /* Inner top catch-light, sitting just inside the hairline */
  --glass-highlight: rgba(255, 255, 255, 0.65);
  /* Brand tint for subtle hover washes */
  --glass-brand-tint: rgba(var(--brand-rgb), 0.10);
  --romman-ease: cubic-bezier(0.22, 1, 0.36, 1);

      --font-gradient-first: #e16c73;
    --font-gradient-second: #a44578;
    --font-gradient-third: #7f2558;
    --font-gradient-fourth: #564886;
    --font-gradient-fifth: #8192c9;
    --font-gradient-sixth: #84c5db;
    --font-gradient-seventh: #4288a7;
    --font-gradient-eighth: #156277;

  /* ---- Brand gradient: ONE ramp, TWO crops ------------------------------
     The eight --font-gradient-* stops above run rose -> plum -> periwinkle ->
     cyan -> teal. Sampled by 0.8rem uppercase type with 0.16em tracking those
     thin strokes integrate into a single iridescent sheen, which is why the
     kicker works. Poured into a filled 180x48 button the SAME ramp reads as
     five literal bands instead: only the first stop is warm, so the CTA is
     periwinkle by 45% and teal by 100% -- the brand red is gone -- and a white
     label measures 1.9:1 over the cyan stop at 60% and 3.2:1 over the rose at
     0%. Both fail AA. That is the "too many colours" problem, and it is a
     property of the surface rather than of the ramp.

     So the ramp is cropped per surface instead of being reused verbatim:

       --brand-gradient-text  the full iridescent ramp. Glyph strokes only:
                              short, tracked, uppercase label text.
       --brand-gradient       the warm crop, anchored on brand red. Filled
                              surfaces. Three stops that darken monotonically
                              (0.151 -> 0.135 -> 0.068 relative luminance) so
                              it reads as a lit surface and not as a band, and
                              every stop clears 4.5:1 against a white label
                              (5.2 / 5.7 / 8.9:1).

     Both crops share one angle so they visibly belong together. RTL mirrors it
     (150 -> 210deg): linear-gradient has no logical direction keyword, so
     without this the ramp runs against the reading order in Arabic. */
  --brand-gradient-angle: 150deg;
  --brand-gradient-text: linear-gradient(var(--brand-gradient-angle), var(--font-gradient-first) 0%, var(--font-gradient-second) 30%, var(--font-gradient-fifth) 45%, var(--font-gradient-sixth) 60%, var(--font-gradient-seventh) 80%, var(--font-gradient-eighth) 100%);
  --brand-gradient: linear-gradient(var(--brand-gradient-angle), var(--brand) 0%, var(--font-gradient-second) 55%, var(--brand-strong) 100%);
  /* Third crop, for surfaces that must stay TRANSLUCENT: the brand-tinted icon
     tiles are glass, so an opaque --brand-gradient would kill the blur behind
     them. Same three hues at the alpha those tiles already use (0.42-0.45), so
     it is a drop-in for their `background` shorthand. Needs the two hues in
     channel form, which no existing token carried. */
  --brand-plum-rgb: 164, 69, 120;
  --brand-strong-rgb: 143, 22, 63;
  /* The cool end of the same ramp in channel form — --font-gradient-fifth as
     rgb, for the outermost stop of the "How we work" mark glow. Same reason as
     the two above: rgba() needs channels, and the hex token cannot supply them. */
  --font-gradient-fifth-rgb: 129, 146, 201;
  --brand-gradient-tint: linear-gradient(var(--brand-gradient-angle), rgba(var(--brand-rgb), 0.52) 0%, rgba(var(--brand-plum-rgb), 0.46) 55%, rgba(var(--brand-strong-rgb), 0.52) 100%);

  /* ---- The gradient as ONE solid colour --------------------------------
     For the properties CSS simply cannot ramp: a border. border-image accepts a
     gradient but ignores border-radius, and every bordered thing here is
     rounded; the padding-box/border-box double-background workaround keeps the
     radius but collides with the cards' own glass background and
     backdrop-filter. So borders get the closest solid colour to the gradient
     instead of staying pure brand red.

     "Closest" = the mean of --brand-gradient sampled across its length and
     averaged in OKLAB, not sRGB -- an sRGB mean drags toward grey because the
     space is not perceptually uniform (it gives #a93658, visibly muddier).
     Result #a9375a: an Oklab dE of just 0.081 from brand red, so it still reads
     as the brand rather than as a new hue, while sitting inside the gradient's
     own range. (The iridescent text ramp averages to #8c819e, a grey-lavender
     at dE 0.198 -- far off-brand for an accent, which is why the FILL crop is
     the one that defines this.)

     Measured before swapping: across all nine bordered components the change
     moves composited border contrast by at most 0.18, and every one of them was
     already under the 3:1 non-text minimum with brand red -- the low alphas
     cause that, not the hue. So this is a hue change, not a legibility change.

     Semantic reds do NOT use this: .is-invalid, .romman-consent-error, the
     consent on/off icons and the consent switch keep pure brand red, because
     there red is the message rather than decoration. */
  --brand-blend: #a9375a;
  --brand-blend-rgb: 169, 55, 90;
}

html[dir="rtl"] {
  --brand-gradient-angle: 210deg;
}

/* ---- Apply the glass recipe to real components ------------------------- */

/* Sticky header floats as a container-width light-glass pill. Same material
   as the content containers, so the whole site is one glass system. */
.header.is-scrolled {
  background: transparent !important;
  border-bottom-color: transparent !important;
  box-shadow: none !important;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  padding-top: 10px;
}

.header.is-scrolled .navbar > .container {
  /* Same glassmorphism as the app cards, without the saturation boost.
     No drop shadow: it read as a dark smudge under the floating pill. The
     ring alone defines the pane. */
  background: rgba(255, 255, 255, 0.09);
  border: 1px solid rgba(255, 255, 255, 0.09);
  border-radius: 26px;
  box-shadow: none;
  -webkit-backdrop-filter: blur(var(--glass-blur, 20px)) saturate(160%);
  backdrop-filter: blur(var(--glass-blur, 20px)) saturate(160%);
  padding-inline: 1.4rem;
  transition: background-color 0.3s var(--romman-ease), box-shadow 0.3s var(--romman-ease);
}

/* The pill is 15% transparent, so its contents cannot have one fixed colour:
   over the maroon hero white text reads and dark text doesn't, and over light
   content the reverse. romman-motion.js watches what is actually behind the
   header and sets .is-over-dark / .is-over-light, swapping the logo with it.
   (Theme uses a deep 6-class selector for nav-link colour → !important.) */
.header.is-over-dark .navbar-brand,
.header.is-over-dark .navbar-toggler,
.header.is-over-dark .navbar .navbar-collapse .navbar-nav .nav-item .nav-link,
.header.is-over-dark .romman-locale-toggle {
  color: #fdf4f5 !important;
}

.header.is-over-dark .navbar .navbar-collapse .navbar-nav .nav-item .nav-link:hover,
.header.is-over-dark .navbar .navbar-collapse .navbar-nav .nav-item .nav-link.active {
  color: #ffffff !important;
  background-color: transparent;
}

.header.is-over-dark .romman-locale-toggle {
  border-color: rgba(255, 255, 255, 0.28) !important;
}

.header.is-over-dark .romman-locale-toggle:hover,
.header.is-over-dark .romman-locale-toggle:focus,
.header.is-over-dark .romman-locale-toggle.show {
  background: rgba(255, 255, 255, 0.14);
  border-color: rgba(255, 255, 255, 0.42);
}

/* Over light content the same glass pill takes dark contents */
.header.is-over-light .navbar-brand,
.header.is-over-light .navbar-toggler,
.header.is-over-light .navbar .navbar-collapse .navbar-nav .nav-item .nav-link,
.header.is-over-light .romman-locale-toggle {
  color: #111827 !important;
}

.header.is-over-light .navbar .navbar-collapse .navbar-nav .nav-item .nav-link:hover,
.header.is-over-light .navbar .navbar-collapse .navbar-nav .nav-item .nav-link.active {
  color: var(--brand) !important;
  background-color: transparent;
}

.header.is-over-light .romman-locale-toggle {
  border-color: rgba(15, 23, 42, 0.18) !important;
}

.header.is-over-light .romman-locale-toggle:hover,
.header.is-over-light .romman-locale-toggle:focus,
.header.is-over-light .romman-locale-toggle.show {
  background: rgba(var(--brand-rgb), 0.08);
  border-color: rgba(var(--brand-blend-rgb), 0.35);
}

/* Over light content the white border would vanish, so the ring goes neutral */
.header.is-over-light .navbar > .container {
  border-color: rgba(15, 23, 42, 0.10);
  box-shadow: none;
}

/* Cross-fade the swap so scrolling past a boundary doesn't snap */
.header .navbar-brand,
.header .navbar-toggler,
.header .romman-locale-toggle,
.header .navbar > .container,
.romman-brand-logo {
  transition: color 0.25s var(--romman-ease), border-color 0.25s var(--romman-ease),
    box-shadow 0.25s var(--romman-ease), opacity 0.2s var(--romman-ease);
}

/* Language dropdown. Only the blur and radius here — the fill is set once at the
   top of this file so there is a single place that decides the colour. */
.romman-locale-menu {
  border-radius: var(--glass-radius-sm);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
}

.romman-locale-toggle {
  border-radius: var(--glass-radius-pill);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
}

/* Homepage: featured-app story + feed list become frosted glass panes */
.romman-feature-story,
.romman-feed-list {
  background: var(--glass-card-bg);
  border: 1px solid var(--glass-card-border);
  border-radius: var(--glass-radius);
  box-shadow: var(--glass-card-shadow), inset 0 1px 0 var(--glass-highlight);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  overflow: hidden;
}

.romman-feature-story-image {
  border-radius: var(--glass-radius) 0 0 var(--glass-radius);
}

html[dir="rtl"] .romman-feature-story-image {
  border-radius: 0 var(--glass-radius) var(--glass-radius) 0;
}

.romman-feed-item {
  transition: background-color 0.3s var(--romman-ease);
}

.romman-feed-item:hover {
  background: var(--glass-brand-tint);
}

/* App / job cards on the /apps and /careers pages */
.romman-card,
.romman-job-card,
.romman-glass-card {
  position: relative;
  background: rgba(255, 255, 255, 0.09);
  border: 1px solid rgba(255, 255, 255, 0.09) !important;
  border-radius: 26px !important;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14), 0 14px 34px rgba(0, 0, 0, 0.38) !important;
  -webkit-backdrop-filter: blur(var(--glass-blur, 20px)) saturate(160%) !important;
  backdrop-filter: blur(var(--glass-blur, 20px)) saturate(160%) !important;
  transition: background-color 0.3s var(--romman-ease), box-shadow 0.3s var(--romman-ease);
}

.romman-card:hover,
.romman-job-card:hover {
  transform: translateY(-6px);
  border-color: rgba(var(--brand-blend-rgb), 0.62) !important;
  background: rgba(var(--brand-rgb), 0.1);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.18),
    inset 0 0 30px rgba(var(--brand-rgb), 0.28),
    inset 0 -40px 60px -40px rgba(var(--brand-rgb), 0.5),
    0 16px 28px -12px rgba(0, 0, 0, 0.6);
    transition: background-color 0.3s var(--romman-ease), transform 0.3s var(--romman-ease);
}

/* Cards over imagery / dark contexts */
.romman-network-card,
.romman-story-panel,
.romman-story-mini {
  background: var(--glass-card-bg);
  border: 1px solid var(--glass-card-border);
  border-radius: var(--glass-radius-sm);
  box-shadow: var(--glass-card-shadow), inset 0 1px 0 var(--glass-highlight);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
}

/* Hero stat blocks + career stats — true liquid glass over media */
.romman-hero-stats .romman-stat-block{
  background: var(--glass-over-media-bg);
  border: 1px solid var(--glass-over-media-border);
  border-radius: var(--glass-radius-sm);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.35), 0 18px 40px rgba(0, 0, 0, 0.18);
  -webkit-backdrop-filter: blur(16px) saturate(var(--glass-saturate));
  backdrop-filter: blur(16px) saturate(var(--glass-saturate));
}

/* Platform + social chips */
.romman-platform-chip {
  background: var(--glass-over-media-bg);
  border: 1px solid var(--glass-over-media-border);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.3);
  -webkit-backdrop-filter: blur(12px) saturate(var(--glass-saturate));
  backdrop-filter: blur(12px) saturate(var(--glass-saturate));
}

/* Buttons — soften corners and give the translucent ones a glass body */
.romman-btn-solid,
.romman-btn-ghost,
.romman-btn-dark,
.romman-btn-outline-dark {
  border-radius: 16px;
  transition: box-shadow 0.35s var(--romman-ease), background-color 0.35s var(--romman-ease), border-color 0.35s var(--romman-ease);
}

.romman-btn-ghost {
    border: 1px solid rgba(255, 255, 255, 0.09);
    background: rgba(255, 255, 255, 0.04);
    -webkit-backdrop-filter: blur(var(--glass-blur, 20px)) saturate(160%);
    backdrop-filter: blur(var(--glass-blur, 20px)) saturate(160%);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14), 0 14px 34px rgba(0, 0, 0, 0.38);
}

.romman-btn-ghost:hover {
  background: linear-gradient(150deg, rgba(255, 255, 255, 0.34), rgba(255, 255, 255, 0.16));
  border-color: rgba(255, 255, 255, 0.5);
}

.romman-btn-solid:hover {
  box-shadow: 0 14px 34px rgba(var(--brand-rgb), 0.4);
}

.romman-btn-dark:hover {
  box-shadow: 0 14px 34px rgba(17, 17, 17, 0.32);
}

.romman-btn-outline-dark:hover {
  background: rgba(17, 17, 17, 0.05);
}

/* Soften the app-icon tiles a touch to match the glass rounding */
.romman-app-icon {
  transition: transform 0.4s var(--romman-ease);
}

/* ---- Motion / reveal support ------------------------------------------
   Elements Motion reveals are hidden by JS (not CSS) so content still
   shows if JavaScript fails. We only declare the will-change hint here. */
[data-romman-reveal],
[data-romman-stagger] > *,
[data-romman-lego] > * {
  will-change: transform, opacity;
}

/* Same contract for the band arrival/departure: the hint lives here, the
   hiding is done by the script. Safe to hint opacity on this one — unlike the
   jobs block, nothing inside "How we work" uses backdrop-filter, so promoting
   it cannot create a backdrop root. */
[data-romman-band-target] {
  will-change: transform, opacity;
}

/* Section scroll anchors sit under the fixed header */
#apps,
#careers {
  scroll-margin-top: 96px;
}

/* Ambient animated glow behind the apps showcase so the glass has
   something colourful to refract. Purely decorative. */
.romman-light-section {
  position: relative;
  overflow: hidden;
}

.romman-light-section .romman-aurora {
  position: absolute;
  inset: -20% -10% auto -10%;
  height: 60%;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(40% 60% at 20% 30%, rgba(var(--brand-rgb), 0.22), transparent 60%),
    radial-gradient(45% 55% at 80% 20%, rgba(59, 130, 246, 0.18), transparent 60%);
  filter: blur(30px);
  opacity: 0.9;
}

.romman-light-section .container,
.romman-app-carousel-shell {
  position: relative;
  z-index: 1;
}

/* ==========================================================================
   Careers listing + detail pages — same liquid-glass language
   ========================================================================== */

/* Reusable frosted-glass card (Bootstrap .card overrides need !important) */
/* Page hero band gets an ambient glow behind the glass card */
.romman-page-hero {
  position: relative;
  overflow: hidden;
  background:
    radial-gradient(70% 120% at 12% -10%, rgba(201, 52, 52, 0.30), transparent 60%),
    radial-gradient(60% 100% at 92% 0%,  rgba(201, 52, 52, 0.14), transparent 55%),
    linear-gradient(160deg, #4a0d1a 0%, #330913 55%, #240610 100%);
}

.romman-page-hero::before {
  content: "";
  position: absolute;
  inset: -30% -10% auto -10%;
  height: 85%;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(40% 60% at 18% 25%, rgba(var(--brand-rgb), 0.30), transparent 60%),
    radial-gradient(45% 55% at 82% 12%, rgba(255, 138, 110, 0.14), transparent 60%);
  filter: blur(32px);
}

.romman-page-hero > .container {
  position: relative;
  z-index: 1;
}

/* Header height + the hero's own vertical rhythm. !important is required
   because Bootstrap's py-* utilities are themselves !important; the values
   match what py-5 / py-lg-11 gave before, so spacing is unchanged. */
.romman-page-hero {
  padding-top: calc(var(--romman-header-h) + 3rem) !important;
}

@media (min-width: 992px) {
  .romman-page-hero {
    padding-top: calc(var(--romman-header-h) + 70px) !important;
  }
}

/* Re-tint the hero pill badge to the brand instead of the theme primary */
.romman-page-hero .badge.text-bg-light-primary {
  background: rgba(var(--brand-rgb), 0.12) !important;
  color: var(--brand) !important;
}

.romman-page-intro {
  max-width: 640px;
}

/* Glassy form controls in the filter panel */
.romman-inner-page .form-control,
.romman-inner-page .form-select {
  border-radius: 12px;
  border: 1px solid rgba(15, 23, 42, 0.12);
  background-color: rgba(255, 255, 255, 0.75);
  padding: 0.7rem 0.9rem;
  transition: border-color 0.25s var(--romman-ease), box-shadow 0.25s var(--romman-ease);
}

.romman-inner-page .form-control:focus,
.romman-inner-page .form-select:focus {
  border-color: rgba(var(--brand-blend-rgb), 0.5);
  box-shadow: 0 0 0 0.2rem rgba(var(--brand-rgb), 0.12);
}

/* Brand-align the primary / outline CTAs on the careers + apply flow */
.romman-inner-page .btn-primary {
  background-color: var(--brand);
  background-image: var(--brand-gradient);
  border-color: transparent;
  box-shadow: 0 10px 24px rgba(var(--brand-rgb), 0.22);
  transition: box-shadow 0.35s var(--romman-ease), background-color 0.35s var(--romman-ease), border-color 0.35s var(--romman-ease);
}

.romman-inner-page .btn-primary:hover,
.romman-inner-page .btn-primary:focus {
  background: var(--brand-strong);
  border-color: var(--brand-strong);
  box-shadow: 0 14px 34px rgba(var(--brand-rgb), 0.4);
}

.romman-inner-page .btn-outline-primary {
  color: var(--brand);
  border-color: rgba(var(--brand-blend-rgb), 0.5);
}

.romman-inner-page .btn-outline-primary:hover,
.romman-inner-page .btn-outline-primary:focus {
  background: var(--brand);
  border-color: var(--brand-blend);
  color: #ffffff;
}

/* Job cards get glass globally; make sure the sheen stays clipped */
.romman-job-card {
  overflow: hidden;
}

/* Qualification / responsibility / requirement lists with brand checks */
.romman-detail-list {
  list-style: none;
  margin: 0;
  padding-inline-start: 0;
}

.romman-detail-list li {
  position: relative;
  margin-bottom: 0.85rem;
  padding-inline-start: 2.1rem;
  color: #4b5563;
  line-height: 1.7;
}

.romman-detail-list li::before {
  content: "\2713";
  position: absolute;
  inset-inline-start: 0;
  top: 0.12em;
  width: 22px;
  height: 22px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  font-weight: 700;
  line-height: 1;
  color: #fdf4f5;
  background: rgba(var(--brand-rgb), 0.12);
  border-radius: 7px;
  box-shadow: inset 0 0 0 1px rgba(var(--brand-rgb), 0.28);
}

/* Related-openings items become subtle divided rows */
.romman-faq-item {
  padding: 1.1rem 0;
  border-bottom: 1px solid rgba(15, 23, 42, 0.08);
}

.romman-faq-item:first-child {
  padding-top: 0;
}

.romman-faq-item:last-child {
  padding-bottom: 0;
  border-bottom: 0;
}

.romman-faq-item strong,
.romman-faq-question {
  color: #111827;
  font-size: 1.05rem;
}

/* The question is an <h3> so machine readers can chunk on it; it must not
   inherit heading scale, because visually it is still a list item. */
.romman-faq-question {
  font-weight: 700;
  line-height: 1.45;
}

/* Anchored sections sit under a sticky-free header, but a fragment jump still
   lands tight against the viewport edge without a little breathing room. */
.romman-faq-item[id],
.romman-rich-text :is(h2, h3, h4)[id] {
  scroll-margin-top: 5rem;
}

/* ==========================================================================
   Facts list — the "at a glance" dl on an app page
   ========================================================================== */
.romman-facts {
  display: grid;
  grid-template-columns: minmax(0, auto) minmax(0, 1fr);
  gap: 0.6rem 1.25rem;
  margin: 0;
}

.romman-facts dt {
  color: #6b7280;
  font-size: 0.9rem;
  font-weight: 500;
}

.romman-facts dd {
  margin: 0;
  color: #111827;
  font-size: 0.95rem;
  font-weight: 600;
  text-align: end;
}

/* ==========================================================================
   On-this-page nav — surfaces the description's own section anchors
   ========================================================================== */
.romman-page-toc ul {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  padding: 0;
  margin: 0;
  list-style: none;
}

.romman-page-toc a {
  display: inline-block;
  padding: 0.3rem 0.75rem;
  border: 1px solid rgba(15, 23, 42, 0.1);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.6);
  color: #4b5563;
  font-size: 0.85rem;
  text-decoration: none;
  transition: color 0.2s ease, border-color 0.2s ease;
}

.romman-page-toc a:hover,
.romman-page-toc a:focus-visible {
  border-color: var(--brand);
  color: var(--brand);
}

/* ==========================================================================
   Pagination — brand-red glass pills, matching the CTAs (site-wide)
   ========================================================================== */
.pagination {
  --bs-pagination-color: var(--brand);
  --bs-pagination-bg: rgba(255, 255, 255, 0.72);
  --bs-pagination-border-color: rgba(15, 23, 42, 0.10);
  --bs-pagination-hover-color: #ffffff;
  --bs-pagination-hover-bg: var(--brand);
  --bs-pagination-hover-border-color: var(--brand);
  --bs-pagination-focus-color: #ffffff;
  --bs-pagination-focus-bg: var(--brand);
  --bs-pagination-focus-box-shadow: 0 0 0 0.2rem rgba(var(--brand-rgb), 0.25);
  --bs-pagination-active-color: #ffffff;
  --bs-pagination-active-bg: var(--brand);
  --bs-pagination-active-border-color: var(--brand);
  --bs-pagination-border-radius: 12px;
  gap: 0.4rem;
  flex-wrap: wrap;
}

/* The current page is the one persistent brand fill in the pager, so it takes
   the fill crop like every other primary surface. Bootstrap drives the pill
   from --bs-pagination-active-bg (a background-COLOR), which a gradient cannot
   be fed through, so the image is declared on the rule instead and the variable
   stays as the layer underneath.

   Hover and focus stay flat: they are transient, they transition their
   background-color in 0.15s, and a gradient would snap. The active pill reading
   slightly richer than a hovered one is the correct hierarchy anyway. */
.pagination .page-item.active .page-link,
.romman-immersive .pagination .page-item.active .page-link {
  background-image: var(--brand-gradient);
  border-color: transparent;
}

.pagination .page-link {
  margin: 0;
  min-width: 44px;
  text-align: center;
  font-weight: 600;
  border-radius: 12px;
  -webkit-backdrop-filter: blur(10px) saturate(var(--glass-saturate));
  backdrop-filter: blur(10px) saturate(var(--glass-saturate));
  transition: background-color 0.25s var(--romman-ease), color 0.25s var(--romman-ease),
    box-shadow 0.25s var(--romman-ease), transform 0.25s var(--romman-ease);
}

.pagination .page-link:hover {
  transform: translateY(-1px);
}

.pagination .page-item.active .page-link {
  box-shadow: 0 10px 24px rgba(var(--brand-rgb), 0.28);
}

.pagination .page-item.disabled .page-link {
  background: rgba(255, 255, 255, 0.5);
  color: rgba(15, 23, 42, 0.35);
}

/* ==========================================================================
   Apply page — friendly, animated glass form
   ========================================================================== */
.romman-apply-intro h2 {
  color: #111827;
}

/* Field groups: label lights up in brand red on focus */
.romman-apply-field .form-label {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  font-weight: 600;
  color: #334155;
  transition: color 0.25s var(--romman-ease);
}

.romman-apply-field .form-label i {
  color: #fdf4f5;
  opacity: 0.85;
  font-size: 1.05em;
}

.romman-apply-field:focus-within .form-label {
  color: var(--brand);
}

.romman-field-error {
  margin-top: 0.4rem;
  color: var(--brand);
  font-size: 0.85rem;
  font-weight: 500;
}

.romman-field-optional {
  color: #94a3b8;
  font-weight: 500;
}

/* ==========================================================================
   Phone field — country readout plus national number, in one control
   ========================================================================== */

/* Reads as a single input even though it is a select and a text box. Border and
   focus ring live here rather than on either child. */
.romman-phone-field {
  display: flex;
  align-items: stretch;
  border-radius: 12px;
  border: 1px solid rgba(15, 23, 42, 0.12);
  background-color: rgba(255, 255, 255, 0.75);
  /*
     Deliberately NOT overflow:hidden. The country panel is positioned out of flow
     inside this box, and clipping it left only the sliver that happened to overlap
     the field visible — which read as the phone input being replaced by a search
     box. The toggle rounds its own leading corners instead.
  */
  transition: border-color 0.25s var(--romman-ease), box-shadow 0.25s var(--romman-ease);
}

.romman-phone-field:focus-within {
  border-color: rgba(var(--brand-blend-rgb), 0.5);
  box-shadow: 0 0 0 0.2rem rgba(var(--brand-rgb), 0.12);
}

/* ==========================================================================
   Country picker — a listbox, because an <option> cannot hold a flag
   ========================================================================== */

.romman-country-picker {
  position: relative;
  display: inline-flex;
  align-items: stretch;
  /* Sized by its own content and no more: the number box beside it is what should
     take the remaining room, not the other way round. */
  flex: 0 0 auto;
}

/* Standalone (country of residence) rather than welded into the phone field. */
.romman-country-picker-block {
  display: flex;
  width: 100%;
}

/* Until JS enhances it the native select is the control, so it has to look like
   one; afterwards it is only the value that gets posted. */
.romman-country-picker.is-enhanced .romman-country-native {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  border: 0;
  opacity: 0;
  pointer-events: none;
}

.romman-country-picker:not(.is-enhanced) .romman-country-readout {
  display: none;
}

.romman-picker-toggle,
.romman-country-readout {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  white-space: nowrap;
}

.romman-picker-toggle {
  padding: 0.7rem 0.75rem;
  border: 0;
  border-inline-end: 1px solid rgba(15, 23, 42, 0.12);
  background: rgba(15, 23, 42, 0.03);
  cursor: pointer;
  font: inherit;
  color: inherit;
  /* Logical, so it rounds the leading corner in Arabic too — the field itself no
     longer clips this to shape. */
  border-start-start-radius: 11px;
  border-end-start-radius: 11px;
}

.romman-country-picker-block .romman-picker-toggle {
  width: 100%;
  justify-content: flex-start;
  border: 1px solid rgba(15, 23, 42, 0.12);
  border-radius: 12px;
  background-color: rgba(255, 255, 255, 0.75);
}

.romman-picker-toggle:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: -2px;
}

.romman-country-label {
  overflow: hidden;
  text-overflow: ellipsis;
}

/*
   The open list. Floats over the form rather than pushing it around, in the same
   dark glass as the rest of the site.

   Nearly opaque rather than the 15% fill the cards use: this panel sits over
   arbitrary page content, and text over text is unreadable however pretty the blur.
*/
.romman-picker-panel {
  position: absolute;
  z-index: 20;
  top: calc(100% + 0.35rem);
  inset-inline-start: 0;
  min-width: 280px;
  max-width: 340px;
  padding: 0.5rem;
  border-radius: 14px;
  border: 1px solid rgba(255, 255, 255, 0.16);
  background: rgba(18, 19, 24, 0.94);
  box-shadow: 0 22px 52px rgba(0, 0, 0, 0.50);
  -webkit-backdrop-filter: blur(20px) saturate(var(--glass-saturate));
  backdrop-filter: blur(20px) saturate(var(--glass-saturate));
}

.romman-picker-search {
  width: 100%;
  margin-bottom: 0.4rem;
  padding: 0.5rem 0.65rem;
  border-radius: 10px;
  border: 1px solid rgba(255, 255, 255, 0.22);
  background: rgba(255, 255, 255, 0.10);
  color: #fdf4f5;
  font: inherit;
}

.romman-picker-search::placeholder {
  color: rgba(253, 244, 245, 0.55);
}

.romman-picker-search:focus {
  outline: none;
  border-color: rgba(255, 255, 255, 0.45);
  background: rgba(255, 255, 255, 0.16);
}

.romman-picker-list {
  list-style: none;
  margin: 0;
  padding: 0;
  max-height: 260px;
  overflow-y: auto;
  /* Contains the momentum scroll on iOS instead of passing it to the page. */
  overscroll-behavior: contain;
  scrollbar-width: thin;
  scrollbar-color: rgba(255, 255, 255, 0.28) transparent;
}

.romman-picker-list::-webkit-scrollbar {
  width: 10px;
}

.romman-picker-list::-webkit-scrollbar-thumb {
  border: 3px solid transparent;
  border-radius: 8px;
  background-clip: content-box;
  background-color: rgba(255, 255, 255, 0.28);
}

.romman-picker-item {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.5rem 0.6rem;
  border-radius: 9px;
  cursor: pointer;
}

.romman-picker-item.is-active,
.romman-picker-item:hover {
  background: rgba(255, 255, 255, 0.12);
}

.romman-picker-item[aria-selected="true"] {
  font-weight: 600;
}

.romman-picker-flag {
  flex: 0 0 24px;
  width: 24px;
  height: 18px;
  border-radius: 3px;
  object-fit: cover;
  box-shadow: 0 0 0 1px rgba(15, 23, 42, 0.10);
}

.romman-phone-flag {
  width: 24px;
  height: 18px;
  border-radius: 3px;
  object-fit: cover;
  box-shadow: 0 0 0 1px rgba(15, 23, 42, 0.10);
}

.romman-phone-dial {
  font-weight: 600;
  color: #334155;
  /* Always read the code left to right, even on the Arabic page. */
  direction: ltr;
  unicode-bidi: isolate;
}

.romman-phone-caret {
  font-size: 0.9rem;
  color: #64748b;
}

/*
   The real control: full-size and on top, so a tap or a click opens the platform's
   own country picker. Kept at opacity 0 rather than display:none so it stays
   focusable and announced — the readout beside it is only paint.
*/
.romman-phone-country select {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  border: 0;
  padding: 0;
  opacity: 0;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
}

/*
   min-width was 0, which let flexbox collapse this to ~29px: the toggle beside it is
   white-space:nowrap and cannot shrink, so the input was the only thing that gave.
   A floor in `ch` keeps room for the digits at any container width.
*/
.romman-phone-number {
  flex: 1 1 12ch;
  min-width: 10ch;
  border: 0;
  background: transparent;
  padding: 0.7rem 0.9rem;
  color: #111827;
  /* A phone number is digits: keep it left-to-right in Arabic too. */
  direction: ltr;
  text-align: start;
}

.romman-phone-number:focus {
  outline: none;
}

.romman-phone-field.is-invalid {
  border-color: var(--brand);
}

/* Persistent format hint under an input — for the phone field, which now requires
   a country code and so has to say so before the visitor gets it wrong, not only
   after. A placeholder would vanish the moment they start typing. */
.romman-field-hint {
  display: block;
  margin-top: 0.4rem;
  color: #64748b;
  font-size: 0.85rem;
}

/* Friendly resume drop zone (wraps a hidden file input) */
.romman-file-drop {
  position: relative;
  display: flex;
  align-items: center;
  gap: 1rem;
  width: 100%;
  margin: 0;
  padding: 1.2rem 1.35rem;
  border: 1.5px dashed rgba(var(--brand-blend-rgb), 0.35);
  border-radius: 16px;
  background: rgba(255, 255, 255, 0.6);
  cursor: pointer;
  transition: border-color 0.25s var(--romman-ease), background-color 0.25s var(--romman-ease),
    box-shadow 0.25s var(--romman-ease);
}

/* Visually hidden but still focusable (so `required` validation can anchor
   to it — never use display:none/hidden on a required control). */
.romman-file-input {
  position: absolute;
  inset-inline-start: 1.35rem;
  bottom: 0.6rem;
  width: 1px;
  height: 1px;
  padding: 0;
  opacity: 0;
  overflow: hidden;
}

.romman-file-drop:hover,
.romman-file-drop.is-dragover {
  border-color: var(--brand-blend);
  background: rgba(var(--brand-rgb), 0.06);
  box-shadow: 0 12px 30px rgba(var(--brand-rgb), 0.12);
}

.romman-file-drop.has-file {
  border-style: solid;
  border-color: rgba(var(--brand-blend-rgb), 0.55);
  background: rgba(var(--brand-rgb), 0.05);
}

.romman-file-icon {
  flex: 0 0 46px;
  width: 46px;
  height: 46px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 12px;
  font-size: 1.4rem;
  color: var(--brand);
  background: rgba(var(--brand-rgb), 0.12);
}

.romman-file-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.romman-file-cta {
  color: #111827;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.romman-file-hint {
  color: #64748b;
}

/* "What happens next" vertical stepper */
.romman-stepper {
  list-style: none;
  margin: 0;
  padding: 0;
}

.romman-stepper li {
  position: relative;
  display: flex;
  gap: 1rem;
  padding-bottom: 1.6rem;
}

.romman-stepper li:last-child {
  padding-bottom: 0;
}

.romman-stepper li:not(:last-child)::before {
  content: "";
  position: absolute;
  top: 42px;
  bottom: 6px;
  inset-inline-start: 19px;
  width: 2px;
  background: linear-gradient(rgba(var(--brand-rgb), 0.45), rgba(var(--brand-rgb), 0.05));
}

.romman-step-num {
  flex: 0 0 40px;
  width: 40px;
  height: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 12px;
  font-weight: 800;
  color: #ffffff;
  /* Same category as a primary button: an opaque brand surface carrying a white
     label, so it takes the fill crop. Colour stays underneath the image. */
  background-color: var(--brand);
  background-image: var(--brand-gradient);
  box-shadow: 0 8px 20px rgba(var(--brand-rgb), 0.28);
  z-index: 1;
}

.romman-stepper strong {
  display: block;
  color: #111827;
}

.romman-stepper p {
  margin: 0.25rem 0 0;
  color: #64748b;
  line-height: 1.6;
}

.romman-apply-reassure {
  margin: 1.5rem 0 0;
  color: #556070;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
}

.romman-apply-reassure i {
  color: #fdf4f5;
}

/* --- Job page: the closing apply bar ---------------------------------------
   The only Apply button on an opening used to sit in the hero, above the
   qualifications and requirements — so the visitor met it BEFORE the reasons
   to press it, and had to scroll back up once convinced. This one closes the
   page, where the reading actually ends.

   Deliberately a BAR, not a repeat of the card: it reads as the end of the
   page rather than as a duplicate of the control above it. The copy and the
   button separate onto their own lines below the grid breakpoint, where side
   by side would squeeze the button to two words. */
.romman-job-cta {
  margin-top: 1.5rem;
  padding: 1.5rem 1.75rem;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1rem 1.5rem;
  background: rgba(255, 255, 255, 0.07);
  border: 1px solid rgba(255, 255, 255, 0.09);
  border-radius: 26px;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.13), 0 16px 36px rgba(0, 0, 0, 0.32);
  -webkit-backdrop-filter: blur(var(--glass-blur, 20px)) saturate(160%);
  backdrop-filter: blur(var(--glass-blur, 20px)) saturate(160%);
}

.romman-job-cta-copy strong {
  display: block;
  font-weight: 700;
}

.romman-job-cta-copy p {
  color: #64748b;
  line-height: 1.6;
}

.romman-immersive .romman-job-cta-copy strong {
  color: #ffffff;
}

.romman-immersive .romman-job-cta-copy p {
  color: rgba(255, 255, 255, 0.72);
}

@media (max-width: 575.98px) {
  .romman-job-cta {
    padding: 1.25rem;
  }

  /* Full width here: a pill floating alone under the copy on a narrow screen
     reads as an afterthought rather than as the page's closing action. */
  .romman-job-cta .btn {
    width: 100%;
  }
}

/* --- Auto-fill panel: the fast path, before the form ------------------------
   Deliberately the loudest thing in the card. It is offered first because it is
   the shortest route through the form, and it has to read as an offer rather
   than as the first of eight fields — hence the tint, the border and the copy
   sitting beside the control instead of above it.

   [hidden] until romman-motion.js confirms the browser can do the work: an
   offer that silently does nothing is worse than no offer. */
.romman-autofill-panel {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1rem 1.5rem;
  padding: 1.25rem 1.5rem;
  border-radius: 20px;
  background:
    linear-gradient(135deg, rgba(var(--brand-rgb), 0.20), rgba(110, 137, 255, 0.12)),
    rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(var(--brand-blend-rgb), 0.32);
}

.romman-autofill-panel[hidden] {
  display: none;
}

.romman-autofill-panel-copy strong {
  display: block;
  font-weight: 700;
  color: #ffffff;
}

.romman-autofill-panel-copy strong i {
  color: #fdf4f5;
  margin-inline-end: 0.35rem;
}

.romman-autofill-panel-copy p {
  margin-top: 0.2rem;
  font-size: 0.9rem;
  line-height: 1.55;
  color: rgba(255, 255, 255, 0.72);
  max-width: 46ch;
}

/* The whole label is the target, so the hit area is the button rather than the
   text inside it. */
.romman-autofill-drop {
  position: relative;
  flex-shrink: 0;
  cursor: pointer;
}

.romman-autofill-drop .romman-file-input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  cursor: pointer;
}

.romman-autofill-cta {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.75rem 1.4rem;
  border-radius: 999px;
  background-color: var(--brand);
  background-image: var(--brand-gradient);
  color: #ffffff;
  font-weight: 600;
  white-space: nowrap;
  transition: transform 0.25s var(--romman-ease), box-shadow 0.25s var(--romman-ease);
}

.romman-autofill-drop:hover .romman-autofill-cta,
.romman-autofill-drop:focus-within .romman-autofill-cta {
  transform: translateY(-1px);
  box-shadow: 0 10px 24px rgba(var(--brand-rgb), 0.35);
}

.romman-autofill-drop.has-file .romman-autofill-cta {
  background: rgba(255, 255, 255, 0.14);
}

@media (max-width: 575.98px) {
  .romman-autofill-panel {
    padding: 1.1rem;
  }

  /* Full width below the copy: a pill floating alone beside two lines of text
     is where this design falls apart on a narrow screen. */
  .romman-autofill-drop,
  .romman-autofill-cta {
    width: 100%;
    justify-content: center;
  }
}

/* --- Auto-fill from the CV -------------------------------------------------
   One line that carries the promise before a file is chosen and the outcome
   after. It is the same element throughout on purpose: a result that appears
   somewhere new is a result the applicant has to hunt for. */
.romman-autofill-note[hidden] {
  display: none;
}

.romman-autofill-note {
  margin: 0.65rem 0 0;
  display: flex;
  align-items: flex-start;
  gap: 0.45rem;
  font-size: 0.875rem;
  line-height: 1.55;
  color: rgba(255, 255, 255, 0.62);
}

.romman-autofill-note i {
  color: #fdf4f5;
  /* Optical alignment: the glyph's box is taller than the text's x-height. */
  margin-top: 0.15rem;
}

/* A filled field is marked, not just changed. The applicant is being asked to
   check our guesses, and they cannot check what they cannot find — the tint
   fades on focus, when they have found it. */
.romman-apply-field.is-autofilled :is(.form-control, .form-select, .romman-phone-field, .romman-picker-toggle) {
  box-shadow: inset 0 0 0 1px rgba(var(--brand-rgb), 0.45);
  transition: box-shadow 0.4s var(--romman-ease);
}

.romman-apply-field.is-autofilled:focus-within :is(.form-control, .form-select, .romman-phone-field, .romman-picker-toggle) {
  box-shadow: none;
}

/* Submit button loading spinner */
.romman-spinner {
  display: inline-block;
  width: 1em;
  height: 1em;
  margin-inline-end: 0.5rem;
  vertical-align: -0.15em;
  border: 2px solid rgba(255, 255, 255, 0.5);
  border-top-color: #ffffff;
  border-radius: 50%;
  animation: romman-spin 0.7s linear infinite;
}

@keyframes romman-spin {
  to {
    transform: rotate(360deg);
  }
}

/* ==========================================================================
   Application submitted — the confirmation gets its own screen
   ========================================================================== */

/* Tick medallion: brand disc, ring drawn just inside it, one halo pulse. */
.romman-success-mark {
  position: relative;
  width: 104px;
  height: 104px;
  margin: 0 auto 1.75rem;
  border-radius: 50%;
  background: rgba(var(--brand-rgb), 0.22);
  box-shadow: 0 18px 44px rgba(var(--brand-rgb), 0.32);
}

/* Expands out of the medallion once, so the page lands with a beat. */
.romman-success-mark::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, 0.55);
  animation: romman-success-halo 1.5s var(--romman-ease) 0.35s both;
}

.romman-success-mark svg {
  display: block;
  width: 100%;
  height: 100%;
}

.romman-success-ring {
  fill: none;
  stroke: rgba(255, 255, 255, 0.45);
  stroke-width: 2.5;
  /* r=28 → circumference ≈ 176. Starts fully offset, so the ring draws in. */
  stroke-dasharray: 176;
  stroke-dashoffset: 176;
  /* From 12 o'clock rather than 3 o'clock. */
  transform: rotate(-90deg);
  transform-origin: 50% 50%;
  animation: romman-success-draw 0.75s var(--romman-ease) 0.1s forwards;
}

.romman-success-tick {
  fill: none;
  stroke: #ffffff;
  stroke-width: 5;
  stroke-linecap: round;
  stroke-linejoin: round;
  /* Path length ≈ 40. */
  stroke-dasharray: 42;
  stroke-dashoffset: 42;
  animation: romman-success-draw 0.45s var(--romman-ease) 0.55s forwards;
}

@keyframes romman-success-draw {
  to {
    stroke-dashoffset: 0;
  }
}

@keyframes romman-success-halo {
  from {
    opacity: 0.85;
    transform: scale(0.92);
  }
  to {
    opacity: 0;
    transform: scale(1.5);
  }
}

/* Nothing here conveys meaning through movement — the tick is already drawn
   at its end state, so reduced motion simply skips to it. */
@media (prefers-reduced-motion: reduce) {
  .romman-success-mark::after {
    animation: none;
    opacity: 0;
  }

  .romman-success-ring,
  .romman-success-tick {
    animation: none;
    stroke-dashoffset: 0;
  }
}

/* "What we received" — a receipt of the details we now hold */
.romman-receipt {
  margin: 0;
}

.romman-receipt-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.35rem 1.5rem;
  padding: 0.9rem 0;
  border-bottom: 1px dashed rgba(15, 23, 42, 0.14);
}

.romman-receipt-row:first-child {
  padding-top: 0;
}

.romman-receipt-row:last-child {
  padding-bottom: 0;
  border-bottom: 0;
}

.romman-receipt dt {
  margin: 0;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #64748b;
}

.romman-receipt dd {
  margin: 0;
  font-weight: 600;
  color: #111827;
  text-align: end;
  /* An address with no spaces in it must break rather than widen the card. */
  overflow-wrap: anywhere;
}

.romman-receipt-ref {
  display: inline-block;
  padding: 0.3rem 0.7rem;
  border-radius: 10px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  letter-spacing: 0.08em;
  color: var(--brand);
  background: rgba(var(--brand-rgb), 0.12);
  box-shadow: inset 0 0 0 1px rgba(var(--brand-rgb), 0.28);
}

/* The completed first step carries a tick instead of its number */
.romman-stepper li.is-done .romman-step-num i {
  font-size: 1.2rem;
}

.romman-success-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  justify-content: center;
  margin-top: 3rem;
}

/* ==========================================================================
   Apps listing + detail — glass showcase cards
   ========================================================================== */

/* App icon polish (both pages) */
.romman-app-icon {
  width: 84px;
  height: 84px;
  object-fit: cover;
  border-radius: 20px;
  box-shadow: 0 14px 30px rgba(var(--brand-rgb), 0.18), 0 6px 14px rgba(15, 23, 42, 0.12);
}

.romman-app-icon-lg {
  width: 120px;
  height: 120px;
  border-radius: 28px;
}

/* Whole-card link */
.romman-app-card {
  display: block;
  color: inherit;
  text-decoration: none;
}

.romman-app-card .card-body {
  display: flex;
  flex-direction: column;
}

.romman-app-card-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 1.25rem;
}

.romman-app-card h5 {
  color: #111827;
  transition: color 0.25s var(--romman-ease);
}

.romman-app-card:hover h5 {
  color: var(--brand);
}

.romman-app-card .romman-app-desc {
  color: #64748b;
  line-height: 1.7;
}

/* Featured flag */
.romman-app-flag {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.3rem 0.7rem;
  border-radius: 999px;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--brand);
  background: rgba(var(--brand-rgb), 0.10);
  border: 1px solid rgba(var(--brand-blend-rgb), 0.18);
}

/* Platform chips on light cards */
.romman-app-card-platforms {
  display: inline-flex;
  gap: 0.4rem;
}

.romman-app-chip {
  width: 38px;
  height: 38px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 999px;
  font-size: 1rem;
  color: #334155;
  background: rgba(15, 23, 42, 0.05);
  border: 1px solid rgba(15, 23, 42, 0.08);
  transition: color 0.25s var(--romman-ease), background-color 0.25s var(--romman-ease), border-color 0.25s var(--romman-ease);
}

.romman-app-card:hover .romman-app-chip {
  color: var(--brand);
  border-color: rgba(var(--brand-blend-rgb), 0.25);
  background: rgba(var(--brand-rgb), 0.06);
}

/* CTA affordance row */
.romman-app-card-cta {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  margin-top: auto;
  padding-top: 0.5rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-size: 0.82rem;
  color: var(--brand);
}

.romman-app-card-cta i {
  transition: transform 0.25s var(--romman-ease);
}

.romman-app-card:hover .romman-app-card-cta i {
  transform: translateX(4px);
}

html[dir="rtl"] .romman-app-card-cta i {
  transform: scaleX(-1);
}

html[dir="rtl"] .romman-app-card:hover .romman-app-card-cta i {
  transform: scaleX(-1) translateX(4px);
}

html[dir="rtl"] .romman-inline-link i {
  /* `transform` is ignored on non-replaced inline elements, and the icon <i>
     is display:inline by default — so the flip silently did nothing until we
     gave it a block-ish display. (The card CTA arrow above already worked
     because its icon is display:block.) */
  display: inline-block;
  transform: scaleX(-1);
}

/* Little count chip under the listing heading */
.romman-count-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  margin-top: 1rem;
  padding: 0.35rem 0.85rem;
  border-radius: 999px;
  font-weight: 600;
  font-size: 0.85rem;
  color: var(--brand);
  background: rgba(var(--brand-rgb), 0.08);
  border: 1px solid rgba(var(--brand-blend-rgb), 0.16);
}

/* Detail page rich text */
.romman-rich-text {
  color: #4b5563;
  line-height: 1.8;
}

.romman-rich-text > *:first-child {
  margin-top: 0;
}

.romman-rich-text p {
  margin-bottom: 1rem;
}

.romman-rich-text h1,
.romman-rich-text h2,
.romman-rich-text h3,
.romman-rich-text h4 {
  color: #111827;
  font-weight: 800;
  margin: 1.6rem 0 0.75rem;
}

.romman-rich-text ul,
.romman-rich-text ol {
  padding-inline-start: 1.25rem;
  margin-bottom: 1rem;
}

.romman-rich-text li {
  margin-bottom: 0.4rem;
}

.romman-rich-text a {
  color: var(--brand);
  text-decoration: underline;
}

.romman-rich-text img {
  max-width: 100%;
  height: auto;
  border-radius: 12px;
}

/* Store buttons carry their platform icon */
.romman-store-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
}

/* Respect users who prefer reduced motion: no reveals, no sheens, no
   hover transforms, no ambient animation. */
@media (prefers-reduced-motion: reduce) {
  [data-romman-reveal],
  [data-romman-stagger] > *,
  [data-romman-lego] > *,
  [data-romman-band-target] {
    opacity: 1 !important;
    transform: none !important;
    will-change: auto;
  }

  .romman-light-section .romman-aurora {
    display: none !important;
  }

  * {
    scroll-behavior: auto !important;
  }
}

/* ==========================================================================
   Dark hero band — the ONLY dark surface besides the header pill.
   Page canvas + all body cards stay light, so these overrides are scoped
   strictly to .romman-page-hero.
   ========================================================================== */

/* Hero copy reads light on the maroon band */
.romman-page-hero h1,
.romman-page-hero .fw-bolder {
  color: #ffffff;
}

.romman-page-hero p,
.romman-page-hero .romman-page-intro,
.romman-page-hero .text-muted {
  color: rgba(255, 255, 255, 0.72) !important;
}

/* Pill badge + count chip on the dark band */
.romman-page-hero .badge.text-bg-light-primary {
  background: rgba(var(--brand-rgb), 0.32) !important;
  color: #ffffff !important;
}

.romman-page-hero .romman-count-chip {
  color: #ffffff;
  background: rgba(255, 255, 255, 0.10);
  border-color: rgba(255, 255, 255, 0.24);
}

/* --- About page ------------------------------------------------------------ */
.romman-about-stat {
  display: block;
  color: #111827;
  font-size: clamp(1.9rem, 3vw, 2.6rem);
  font-weight: 800;
  line-height: 1.05;
  /* "300+" is digits followed by a neutral character, so in the Arabic
     layout the bidi algorithm moves the plus to the left and it reads
     "+300". `plaintext` resolves the run's direction from its own content
     instead of the paragraph's — unlike `direction: ltr`, it leaves the
     box's alignment alone, so the figure stays where the layout puts it. */
  unicode-bidi: plaintext;
}

/* `unicode-bidi: plaintext` resolves alignment from the run's own direction
   too, which pulls these figures to the left of their right-aligned labels on
   the Arabic page. A physical alignment overrides that without giving up the
   ordering fix. Applies to both stat treatments — see .romman-metric strong. */
[dir="rtl"] .romman-about-stat,
[dir="rtl"] .romman-metric strong {
  text-align: center;
}

.romman-about-stat-label {
  display: block;
  margin-top: 0.35rem;
  color: #64748b;
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.romman-about-icon {
  width: 52px;
  height: 52px;
  margin-bottom: 1.25rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 14px;
  font-size: 1.5rem;
  color: var(--brand);
  background: rgba(var(--brand-rgb), 0.10);
  box-shadow: inset 0 0 0 1px rgba(var(--brand-rgb), 0.18);
}

/* Hero keeps its normal height — the BACKDROP is what spans the page.
   !important is required: the theme's .bg-light-primary utility sets
   background-color with !important and would otherwise show a light panel
   on top of the page-wide maroon backdrop. */
.romman-immersive .romman-page-hero {
  background: transparent !important;
}

/* --- App cards: glassmorphism on the maroon backdrop -------------------- */

/* A 15% fill lets the maroon read straight through, so the text is light —
   tinted warm rather than pure white so it belongs to the background instead
   of sitting on top of it. */
/* This !important is what makes `.romman-app-card:hover h5 { color: var(--brand) }`
   and `.romman-app-card-cta { color: var(--brand) }` DEAD on every immersive page
   -- measured with forced pseudo-states: neither changes colour on hover, both
   stay #fdf4f5. Harmless, but do not read those two rules as brand-red elements
   waiting to be gradiented; they never paint brand red anywhere they render. */
.romman-immersive .romman-app-card :is(h5, .romman-app-card-cta) {
  color: #fdf4f5 !important;
}

.romman-immersive .romman-app-card .romman-app-desc {
  color: rgba(253, 244, 245, 0.72) !important;
}

.romman-immersive .romman-app-chip {
  color: rgba(253, 244, 245, 0.90);
  background: rgba(255, 255, 255, 0.12);
  border-color: rgba(255, 255, 255, 0.22);
}

.romman-immersive .romman-app-flag {
  color: #fdf4f5;
  background: rgba(var(--brand-rgb), 0.45);
  border-color: rgba(255, 255, 255, 0.26);
  /* 0.45 alpha is the threshold where the tint crop actually reads: a two-hue
     wash at 0.22 measured indistinguishable from flat on a 52px tile, which is
     why the 0.06-0.32 alpha chips and card-hover washes elsewhere are left
     alone rather than gradiented for consistency's sake. */
  background-image: var(--brand-gradient-tint);
}

/* Pagination sits on the dark backdrop now */
.romman-immersive .pagination {
  --bs-pagination-color: #ffffff;
  --bs-pagination-bg: rgba(255, 255, 255, 0.10);
  --bs-pagination-border-color: rgba(255, 255, 255, 0.20);
  --bs-pagination-hover-bg: rgba(255, 255, 255, 0.22);
  --bs-pagination-hover-border-color: rgba(255, 255, 255, 0.35);
  --bs-pagination-hover-color: #ffffff;
  --bs-pagination-active-bg: var(--brand);
  --bs-pagination-active-border-color: var(--brand);
}

.romman-immersive .pagination .page-item.disabled .page-link {
  background: rgba(255, 255, 255, 0.05);
  color: rgba(255, 255, 255, 0.35);
}

/* The footer keeps its own light background, closing the page off instead of
   bleeding into the maroon. It must be positioned so it paints ABOVE the
   fixed rain layer (which would otherwise show through it). */
/* On the maroon page the footer pane keeps the glass but flips to a white
   ring and light contents, exactly like the nav pill does there. */

:is(.romman-immersive, .romman-home) .footer-part .romman-social-icon {
  background: rgba(255, 255, 255, 0.12);
  border-color: rgba(255, 255, 255, 0.24);
  color: #fdf4f5;
}

:is(.romman-immersive, .romman-home) .footer-part .romman-social-icon:hover {
  background: rgba(255, 255, 255, 0.20);
  border-color: rgba(255, 255, 255, 0.40);
  color: #ffffff;
}


/* ==========================================================================
   Glass everywhere — the remaining non-glass surfaces adopt the same
   material. Fill opacity is raised for anything that carries body text or
   form input, because 15% over a light canvas leaves text unreadable; the
   blur, ring and shadow are the shared part of the recipe.
   ========================================================================== */

/* Form controls */
.romman-inner-page .form-control,
.romman-inner-page .form-select,
.romman-inner-page textarea.form-control {
  background-color: rgba(255, 255, 255, 0.45);
  border: 1.5px solid rgba(255, 255, 255, 0.35);
  box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.06);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
}

.romman-inner-page .form-control:focus,
.romman-inner-page .form-select:focus,
.romman-inner-page textarea.form-control:focus {
  background-color: rgba(255, 255, 255, 0.62);
  border-color: rgba(var(--brand-blend-rgb), 0.55);
  box-shadow: 0 0 0 0.2rem rgba(var(--brand-rgb), 0.18);
}

/* Resume drop zone */
.romman-file-drop {
  background: rgba(255, 255, 255, 0.35);
  border: 1.5px dashed rgba(var(--brand-blend-rgb), 0.45);
  box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.05);
  -webkit-backdrop-filter: blur(var(--glass-blur));
  backdrop-filter: blur(var(--glass-blur));
}

.romman-file-icon {
  background: rgba(var(--brand-rgb), 0.14);
  box-shadow: inset 0 0 0 1px rgba(var(--brand-rgb), 0.22);
}

/* Language dropdown + mobile menu — panels floating over the page */
.romman-locale-menu {
  background: rgba(18, 19, 24, 0.94) !important;
  border: 1.5px solid rgba(255, 255, 255, 0.16) !important;
  box-shadow: 0 22px 52px 0 rgba(0, 0, 0, 0.50) !important;
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
}

/* The mobile drawer is the nav in another shape, so it takes the header pill's
   exact material — 15% white, a 1.5px ring, blur(20px) — instead of the near
   opaque white pane it used to be, which was the one light surface left on a
   black site. */
.offcanvas {
  /* Bootstrap draws the drawer's edge with a PHYSICAL border-right on
     .offcanvas-start, which outlives a logical border-inline-end here. Setting
     its variable instead recolours whichever edge the placement uses. */
  --bs-offcanvas-border-color: rgba(255, 255, 255, 0.20);
  /* The theme zeroes this, which also killed the logical border below — the
     drawer had no edge at all. 1.5px matches the pill's ring. */
  --bs-offcanvas-border-width: 1.5px;
  background: rgba(255, 255, 255, 0.15) !important;
  border-inline-end: 1.5px solid rgba(255, 255, 255, 0.20);
  box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.30);
  -webkit-backdrop-filter: blur(20px);
  backdrop-filter: blur(20px);
}

.offcanvas .offcanvas-header {
  border-bottom-color: rgba(255, 255, 255, 0.16) !important;
}

.offcanvas-title {
  color: #fdf4f5;
}

/* The close glyph ships as a dark SVG; invert it rather than swap the asset.
   Bootstrap 5.3 routes it through a variable, so set both for older builds. */
.offcanvas .btn-close {
  --bs-btn-close-filter: invert(1) grayscale(100%) brightness(200%);
  filter: invert(1) grayscale(100%) brightness(200%);
  opacity: 0.75;
}

.offcanvas .btn-close:hover,
.offcanvas .btn-close:focus {
  opacity: 1;
}

/* Nav links inside the drawer. The theme reaches these with a deep selector,
   hence !important — the same reason the header's own links need it. */
.offcanvas .navbar-nav .nav-item .nav-link {
  color: rgba(253, 244, 245, 0.82) !important;
}

.offcanvas .navbar-nav .nav-item .nav-link:hover,
.offcanvas .navbar-nav .nav-item .nav-link.active {
  color: #ffffff !important;
  background-color: transparent;
}

/* Bootstrap's scrim is what keeps 15% white legible over page content. */
.offcanvas-backdrop.show {
  opacity: 0.7;
}

/* Pagination pills */
.pagination .page-link {
  border-width: 1.5px;
  box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.06);
}

/* Secondary / outline buttons become glass; solid brand CTAs stay solid so
   they keep reading as the primary action. */
.romman-inner-page .btn-outline-primary {
  background: rgba(255, 255, 255, 0.30);
  border-width: 1.5px;
  -webkit-backdrop-filter: blur(var(--glass-blur));
  backdrop-filter: blur(var(--glass-blur));
}

/* Stat + value tiles on the About page */
.romman-about-icon {
  background: rgba(var(--brand-rgb), 0.12);
  box-shadow: inset 0 0 0 1px rgba(var(--brand-rgb), 0.20);
  -webkit-backdrop-filter: blur(var(--glass-blur));
  backdrop-filter: blur(var(--glass-blur));
}

/* Alerts (validation / empty states) */
.romman-inner-page .alert {
  background: rgba(255, 255, 255, 0.45) !important;
  border: 1.5px solid rgba(255, 255, 255, 0.35) !important;
  box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.08);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
}

/* On the dark apps page these same surfaces need light-on-dark treatment */
.romman-immersive .pagination .page-link {
  border-color: rgba(255, 255, 255, 0.22);
}

/* Was 80% white, which is what made this the one light panel left on a black
   site. Kept as a rule rather than deleted so the immersive pages cannot inherit
   something lighter from elsewhere. */
.romman-immersive .romman-locale-menu {
  background: rgba(18, 19, 24, 0.94) !important;
}

/* ==========================================================================
   Immersive listing pages — cards + controls on the maroon backdrop
   (shared by the apps and careers listings)
   ========================================================================== */

/* Panels take the same glassmorphism as the app cards */
/* A 15% fill lets the maroon through, so contents go light */
.romman-immersive :is(.romman-glass-card, .romman-job-card) :is(h1, h2, h3, h4, h5, h6, strong, .fw-bolder) {
  color: #fdf4f5 !important;
}

.romman-immersive :is(.romman-glass-card, .romman-job-card) :is(p, span, small, li, label, .text-muted) {
  color: rgba(253, 244, 245, 0.72) !important;
}

/* Form controls sitting on dark glass */
.romman-immersive .form-control,
.romman-immersive .form-select,
.romman-immersive .romman-phone-field,
.romman-immersive textarea.form-control {
  background-color: rgba(255, 255, 255, 0.05);
  border-color: rgba(255, 255, 255, 0.22);
  color: #fdf4f5;
}

/* The phone field's own parts, which are not .form-control */
.romman-immersive .romman-picker-toggle {
  background: rgba(255, 255, 255, 0.08);
  border-inline-end-color: rgba(255, 255, 255, 0.22);
  color: #fdf4f5;
}

.romman-immersive .romman-country-picker-block .romman-picker-toggle {
  background-color: rgba(255, 255, 255, 0.12);
  border-color: rgba(255, 255, 255, 0.22);
}

/*
   The panel's own copy. Three classes plus !important is not decoration: the broad
   light-copy rule above targets every p/span/small/li inside a glass card with
   !important, and these rows ARE li and span inside one. Without outranking it, the
   country names inherit that colour — which is how they ended up near-white on a
   white panel.
*/
.romman-immersive .romman-glass-card .romman-picker-item,
.romman-immersive .romman-glass-card .romman-picker-item span {
  color: rgba(253, 244, 245, 0.88) !important;
}

.romman-immersive .romman-glass-card .romman-picker-item[aria-selected="true"],
.romman-immersive .romman-glass-card .romman-picker-item[aria-selected="true"] span,
.romman-immersive .romman-glass-card .romman-picker-item.is-active,
.romman-immersive .romman-glass-card .romman-picker-item.is-active span,
.romman-immersive .romman-glass-card .romman-picker-item:hover span {
  color: #ffffff !important;
}

/* The search box is an <input>, so the copy rule misses it, but the .form-control
   styling misses it too — it needs saying explicitly. */
.romman-immersive .romman-glass-card .romman-picker-search {
  color: #fdf4f5 !important;
}

.romman-immersive .romman-phone-dial,
.romman-immersive .romman-country-label,
.romman-immersive .romman-phone-number {
  color: #fdf4f5;
}

.romman-immersive .romman-phone-caret {
  color: rgba(253, 244, 245, 0.70);
}

.romman-immersive .romman-phone-number::placeholder {
  color: rgba(253, 244, 245, 0.55);
}

.romman-immersive .romman-phone-field:focus-within {
  background-color: rgba(255, 255, 255, 0.20);
  border-color: rgba(255, 255, 255, 0.45);
  box-shadow: 0 0 0 0.2rem rgba(255, 255, 255, 0.14);
}

.romman-immersive .form-control::placeholder {
  color: rgba(253, 244, 245, 0.55);
}

.romman-immersive .form-select {
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23fdf4f5' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
}

.romman-immersive .form-control:focus,
.romman-immersive .form-select:focus,
.romman-immersive textarea.form-control:focus {
  background-color: rgba(255, 255, 255, 0.20);
  border-color: rgba(255, 255, 255, 0.45);
  color: #fdf4f5;
  box-shadow: 0 0 0 0.2rem rgba(255, 255, 255, 0.14);
}

/* Options render in the OS menu, which is light — keep their text dark */
.romman-immersive .form-select option {
  color: #111827;
}

/* Light Bootstrap tag badges → translucent chips */
.romman-immersive .badge.text-bg-light {
  background: rgba(255, 255, 255, 0.14) !important;
  color: rgba(253, 244, 245, 0.90) !important;
}

/* "Clear filters" secondary button */
.romman-immersive .btn-light {
  background: rgba(255, 255, 255, 0.14);
  border: 1.5px solid rgba(255, 255, 255, 0.22);
  color: #fdf4f5;
  -webkit-backdrop-filter: blur(20px);
  backdrop-filter: blur(20px);
}

.romman-immersive .btn-light:hover,
.romman-immersive .btn-light:focus {
  background: rgba(255, 255, 255, 0.22);
  border-color: rgba(255, 255, 255, 0.38);
  color: #ffffff;
}

/* Empty-state / validation alerts */
.romman-immersive .alert {
  background: rgba(255, 255, 255, 0.12) !important;
  border: 1.5px solid rgba(255, 255, 255, 0.22) !important;
  color: rgba(253, 244, 245, 0.88) !important;
}

/* ==========================================================================
   Immersive detail pages (apps/show, careers/show, apply)
   Hero copy sits directly on the band (same as the listing heroes), so the
   band-level light-copy rules cover it. These handle everything below it.
   ========================================================================== */

/* Headings and links that sit directly on the page, outside any card */
.romman-immersive :is(.romman-section-title, .romman-inline-link),
.romman-immersive .text-dark {
  color: #fdf4f5 !important;
}

.romman-immersive .romman-inline-link:hover {
  color: #ffffff !important;
}

/* App description rich text */
.romman-immersive .romman-rich-text {
  color: rgba(253, 244, 245, 0.72);
}

.romman-immersive .romman-rich-text :is(h1, h2, h3, h4) {
  color: #fdf4f5;
}

.romman-immersive .romman-rich-text a {
  color: #ffffff;
}

/* Qualification / responsibility lists */
.romman-immersive .romman-detail-list li {
  color: rgba(253, 244, 245, 0.78);
}

.romman-immersive .romman-detail-list li::before {
  color: #fdf4f5;
  background: rgba(255, 255, 255, 0.09);
  border: 1px solid rgba(255, 255, 255, 0.09) !important;
  border-radius: 26px !important;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.13), 0 16px 36px rgba(0, 0, 0, 0.32);
  -webkit-backdrop-filter: blur(var(--glass-blur, 20px)) saturate(160%) !important;
  backdrop-filter: blur(var(--glass-blur, 20px)) saturate(160%) !important;
  transition: background-color 0.3s var(--romman-ease), box-shadow 0.3s var(--romman-ease);
}

/* FAQ / related-opening rows */
.romman-immersive .romman-faq-item {
  border-bottom-color: rgba(255, 255, 255, 0.14);
}

/* Facts list. The blanket glass-card rules above cover p/span/li but not
   dt/dd, so without these the values keep their light-theme near-black and
   all but disappear against the maroon. */
.romman-immersive .romman-glass-card .romman-facts dt {
  color: rgba(253, 244, 245, 0.66);
}

.romman-immersive .romman-glass-card .romman-facts dd {
  color: #fdf4f5;
}

/* On-this-page pills follow the same glass treatment as the other chips */
.romman-immersive .romman-page-toc a {
  background: rgba(255, 255, 255, 0.1);
  border-color: rgba(255, 255, 255, 0.18);
  color: rgba(253, 244, 245, 0.82);
}

.romman-immersive .romman-page-toc a:hover,
.romman-immersive .romman-page-toc a:focus-visible {
  background: rgba(255, 255, 255, 0.18);
  border-color: rgba(255, 255, 255, 0.4);
  color: #fdf4f5;
}

/* Outline buttons (App Store, related roles) on dark glass */
.romman-immersive .btn-outline-primary {
  background: rgba(255, 255, 255, 0.10);
  border-color: rgba(255, 255, 255, 0.35);
  color: #fdf4f5;
}

.romman-immersive .btn-outline-primary:hover,
.romman-immersive .btn-outline-primary:focus {
  background: var(--brand);
  border-color: var(--brand-blend);
  color: #ffffff;
}

/* Apply form: resume drop zone */
.romman-immersive .romman-file-drop {
  background: rgba(255, 255, 255, 0.10);
  border-color: rgba(255, 255, 255, 0.30);
}

.romman-immersive .romman-file-drop:hover,
.romman-immersive .romman-file-drop.is-dragover,
.romman-immersive .romman-file-drop.has-file {
  background: rgba(var(--brand-rgb), 0.24);
  border-color: rgba(255, 255, 255, 0.45);
}

.romman-immersive .romman-file-cta {
  color: #fdf4f5;
}

.romman-immersive .romman-file-hint,
.romman-immersive .romman-field-hint,
.romman-immersive .romman-field-optional {
  color: rgba(253, 244, 245, 0.60);
}

.romman-immersive .romman-file-icon {
  color: #fdf4f5;
  background: rgba(var(--brand-rgb), 0.45);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.24);
}

/* Field labels light up white rather than brand on a dark pane */
.romman-immersive .romman-apply-field:focus-within .form-label {
  color: #ffffff !important;
}

.romman-immersive .romman-field-error {
  color: #ffb4b4 !important;
}

/* About-page style icon tiles, if they appear on a dark page */
.romman-immersive .romman-about-icon {
  color: #fdf4f5;
  background: rgba(var(--brand-rgb), 0.42);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.24);
}

/* Both tinted icon tiles take the translucent crop. Declared after the two
   rules above rather than merged into them: those set `background` (a shorthand,
   which resets background-image to none), and they score the same (0,2,0) as
   this does, so source order is the only thing that decides it. The flat tint
   stays underneath as the fallback layer.

   The glyphs keep their warm-white fill -- see the note on the gradient-text
   utility for why the ramp goes on the tile and not on the icon here. */
.romman-immersive .romman-about-icon,
.romman-immersive .romman-file-icon {
  background-image: var(--brand-gradient-tint);
}

/* Solid-fill badges keep crisp white text — the broad muted-span rule above
   would otherwise wash out the numerals on the brand-filled circles. Needs
   three classes to outrank that rule: `:is()` takes the specificity of its
   most specific argument, so the muted-span selector scores (0,2,1). */
.romman-immersive :is(.romman-glass-card, .romman-job-card) .romman-step-num,
.romman-immersive :is(.romman-glass-card, .romman-job-card) .badge.text-bg-primary {
  color: #ffffff !important;
}

/* Application-submitted receipt on dark glass. dt/dd are not in the broad
   light-copy rule above (it lists p/span/small/li/label), so they need their
   own colours here or they would stay near-black on the maroon pane. */
.romman-immersive .romman-receipt-row {
  border-bottom-color: rgba(255, 255, 255, 0.18);
}

.romman-immersive .romman-receipt dt {
  color: rgba(253, 244, 245, 0.60);
}

.romman-immersive .romman-receipt dd {
  color: #fdf4f5;
}

/* The reference chip IS a span inside the card, so it does hit that rule and
   needs the same three-class + !important treatment to keep its own colour. */
.romman-immersive .romman-glass-card .romman-receipt-ref {
  color: #fdf4f5 !important;
  background: rgba(var(--brand-rgb), 0.45);
  background-image: var(--brand-gradient-tint);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.28);
}

/* ==========================================================================
   RTL select caret + pagination summary
   ========================================================================== */

/* The Bootstrap build is LTR-only and pins the select caret to the right edge
   with a physical (not logical) background-position, so in Arabic it lands on
   the same side the text starts and the two collide. Flip the caret in RTL,
   and reserve room for it on whichever side it ends up on. */
.romman-inner-page .form-select,
.form-select {
  padding-inline-end: 2.5rem;
}

html[dir="rtl"] .form-select {
  background-position: 12px 50%;
}

/* Laravel's pagination summary ("Showing 1 to 9 of 16 results") lives outside
   any card, so the card text rules never reach it — on the dark listings it
   was grey-on-dark. */
.romman-immersive nav .small,
.romman-immersive nav .small .fw-semibold,
.romman-immersive nav .text-muted {
  color: rgba(253, 244, 245, 0.75) !important;
}

.romman-immersive nav .small .fw-semibold {
  color: #fdf4f5 !important;
}

/* ==========================================================================
   About + Support on the immersive canvas
   ========================================================================== */

/* Stat numerals are display type — they stay solid, not muted. Needs three
   classes to outrank the muted-span rule, which scores (0,2,1) via :is(). */
.romman-immersive :is(.romman-glass-card, .romman-job-card) .romman-about-stat,
.romman-immersive :is(.romman-glass-card, .romman-job-card) .romman-about-icon,
.romman-immersive :is(.romman-glass-card, .romman-job-card) .romman-file-icon {
  color: #fdf4f5 !important;
}

/* Social icons in the contact sidebar match the footer's treatment */
.romman-immersive .romman-glass-card .romman-social-icon {
  background: rgba(255, 255, 255, 0.12);
  border-color: rgba(255, 255, 255, 0.24);
  color: #fdf4f5;
}

.romman-immersive .romman-glass-card .romman-social-icon:hover {
  background: rgba(255, 255, 255, 0.20);
  border-color: rgba(255, 255, 255, 0.40);
  color: #ffffff;
}

/* The icon rain is a positioned layer (fixed, z-index:0), so it paints ABOVE
   plain in-flow content: section headings sitting directly on the page were
   rendering *under* the falling icons, which looked like the text was
   semi-transparent. Cards never showed it because they are position:relative.
   Lift the page's sections into their own layer above the rain. The hero is
   excluded so the rain stays in the root stacking context (its own content is
   already lifted by .romman-page-hero > .container). */
.romman-immersive .body-wrapper > section:not(.romman-page-hero) {
  position: relative;
  z-index: 1;
}

/* ==========================================================================
   Footer: legal + social on one side, brand + store badges on the other
   ========================================================================== */

.romman-footer-logo {
  height: 70px;
  width: auto;
  display: block;
}

/* The brand column is end-aligned on desktop, so its logo has to follow —
   the Bootstrap build is LTR-only, hence the explicit RTL flip below. */
@media (min-width: 992px) {
  .romman-footer-brand .romman-footer-logo {
    margin-inline-start: auto;
  }
}

/* Below the separator: nav + copyright on one side, store badges on the
   other, centred against each other on one row. */
.romman-footer-base {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 1.25rem 2rem;
}

.romman-footer-base-text {
  /* Lets the text block shrink before the badges are forced to wrap. */
  min-width: 0;
  flex: 1 1 auto;
}

/* auto margin rather than the parent's justify-content: it keeps the badges
   on the far edge whether they share the row or wrap onto their own line.
   Direction-aware, so Arabic mirrors it. */
.romman-store-badges {
  margin-inline-start: auto;
}

/* The copyright's top margin is what separates it from the nav ABOVE it; on
   the row it must not also push the pair off-centre against the badges. */
.romman-footer-base .romman-footer-copy {
  margin-bottom: 0;
}

/* Only text-align needs an RTL override: Bootstrap's .text-lg-end is physical
   (`right`). The logo's `margin-inline-start: auto` and the badges' flex-end
   are already direction-aware and flip on their own — overriding those pushed
   them back to the wrong edge. */
html[dir="rtl"] .footer-part .text-lg-end {
  text-align: left !important;
}

.romman-store-badge {
  height: 42px;
  width: auto;
  display: block;
  transition: transform 0.25s var(--romman-ease), opacity 0.25s var(--romman-ease);
}

.romman-store-badges a:hover .romman-store-badge {
  transform: translateY(-2px);
  opacity: 0.9;
}

/* No plate behind the badges: the official artwork already carries white
   lettering and (on the Apple "blk" badge) its own outline, so it reads fine
   on the dark footer. A plate just added a white box around each one. */
.romman-store-badges a {
  display: inline-flex;
}

.romman-footer-legal p {
  max-width: 46ch;
}

/* Footer nav — sits under the copyright line, so it runs horizontally */
.romman-footer-nav {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.5rem;
}

.romman-footer-separator {
  height: 1px;
  margin: 1.15rem 0;
  border: 0;
  opacity: 1;
  background: rgba(15, 23, 42, 0.12);
}

:is(.romman-immersive, .romman-home) .romman-footer-separator {
  background: rgba(255, 255, 255, 0.16);
}

.romman-footer-nav a {
  color: #2a3547;
  font-weight: 600;
  font-size: 0.95rem;
  width: fit-content;
  transition: color 0.25s var(--romman-ease), transform 0.25s var(--romman-ease);
}

.romman-footer-nav a:hover {
  color: var(--brand);
  transform: translateX(3px);
}

html[dir="rtl"] .romman-footer-nav a:hover {
  transform: translateX(-3px);
}

:is(.romman-immersive, .romman-home) .romman-footer-nav a {
  color: rgba(253, 244, 245, 0.78);
}

:is(.romman-immersive, .romman-home) .romman-footer-nav a:hover {
  color: #ffffff;
}

/* Footer: "Follow us on" label + the copyright line under the nav */
.romman-footer-follow {
  margin: 0 0 0.85rem;
  color: #5d7287;
  font-size: 0.85rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.romman-footer-copy {
  margin: 1rem 0 0;
  font-size: 0.92rem;
}

/* Three classes: the generic footer-paragraph rule uses :is() and scores
   (0,2,1), so a two-class override would silently lose. */
:is(.romman-immersive, .romman-home) .footer-part .romman-footer-copy {
  color: rgba(253, 244, 245, 0.60) !important;
}

:is(.romman-immersive, .romman-home) .footer-part .romman-footer-follow {
  color: rgba(253, 244, 245, 0.68) !important;
}

/* ==========================================================================
   Shared button hover — white face, black edge
   Declared last on purpose: the per-variant hovers above (.romman-btn-ghost,
   .btn-primary, Bootstrap's own .btn:hover) carry the same specificity, so
   source order is what decides this. No !important needed.

   The page-scoped selectors are not redundant: .romman-inner-page .btn-primary
   and .romman-immersive .btn-outline-primary / .btn-light each score (0,3,0)
   and would otherwise outrank a bare .btn:hover on those pages.
   ========================================================================== */
/* The header's language pill is excluded: it runs its own contrast-aware
   hover that adapts to whatever it is floating over, and it outranks this rule
   for background but not for colour — which left black text on a dark pill. */
.btn:not(.romman-locale-toggle):hover,
.btn:not(.romman-locale-toggle):focus-visible,
.romman-inner-page .btn:not(.romman-locale-toggle):hover,
.romman-inner-page .btn:not(.romman-locale-toggle):focus-visible,
.romman-immersive .btn:not(.romman-locale-toggle):hover,
.romman-immersive .btn:not(.romman-locale-toggle):focus-visible {
  background: #ffffff;
  border-color: #111111;
  /* Not optional: several variants set a white label, which would disappear
     on a white face. */
  color: #111111;
}

/* The solid variant threw a red glow on hover, which reads as a smear under a
   white button. */
.romman-btn-solid:hover,
.romman-btn-solid:focus-visible {
  box-shadow: 0 12px 28px rgba(0, 0, 0, 0.28);
}

/* Bootstrap 5 drives .btn-primary etc. through custom properties; matching
   them here keeps any rule that reads the variables in step. */
.btn:not(.romman-locale-toggle):hover,
.btn:not(.romman-locale-toggle):focus-visible {
  --bs-btn-hover-bg: #ffffff;
  --bs-btn-hover-border-color: #111111;
  --bs-btn-hover-color: #111111;
}

/* Job posting sections. Which one lands last depends on what the opening
   actually has filled in, so the gap is declared between blocks instead of
   below each one — nothing can strand trailing whitespace in the card. */
.romman-job-block + .romman-job-block {
  margin-top: 3rem;
}

/* ==========================================================================
   Cookie consent — popup, preference panel, policy page
   Rendered hidden by Blade and revealed by romman-consent.js, so nothing here
   may depend on JS for *legibility* — only for motion.

   Sits above the header (z-index 1100) because a consent request a sticky nav
   can cover is not a consent request. Written with logical properties
   throughout: the same rules have to hold in Arabic RTL.

   Entrances are transitions off @starting-style, and that choice is load-bearing:
   the declared state of every one of these elements is the *visible* one, and the
   fade-in is only a before-change style. So if the motion never runs at all — a
   tab whose document timeline is frozen because it has not rendered yet, reduced
   motion, a browser without @starting-style — the element simply appears. It
   cannot end up present-but-invisible, which for a consent request means the
   visitor is asked nothing while the site behaves as though they were.

   Two earlier attempts both failed that way and are worth not repeating: a
   JS-toggled .is-visible class (the frame callback never fires in a hidden
   document) and keyframe animations (running, but pinned to frame 0 by the same
   frozen timeline).
   ========================================================================== */

.romman-consent-scrim {
  position: fixed;
  inset: 0;
  z-index: 1200;
  background: rgba(10, 12, 18, 0.55);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  opacity: 1;
  transition: opacity 0.3s var(--romman-ease);
}

@starting-style {
  .romman-consent-scrim {
    opacity: 0;
  }
}

/* Dark glass, matching the site's own surface. Deliberately far more opaque than
   .romman-immersive .romman-glass-card's 15% fill: that card sits on a known dark
   canvas, whereas this floats over arbitrary page content — the maroon hero, a
   white card, an app screenshot — and the copy has to stay readable over all of
   them. The blur still lets the backdrop register underneath, so it reads as the
   same material rather than a flat black box. */
.romman-consent-popup,
.romman-consent-panel {
  position: fixed;
  z-index: 1210;
  background: rgba(18, 19, 24, 0.94);
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: var(--glass-radius);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.10), 0 18px 48px rgba(0, 0, 0, 0.48);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  color: rgba(253, 244, 245, 0.82);
  padding: 1.5rem;
}

.romman-consent-popup {
  inset-block-end: 1rem;
  inset-inline: 1rem;
  max-width: 40rem;
  margin-inline: auto;
  opacity: 1;
  transform: none;
  transition: opacity 0.35s var(--romman-ease), transform 0.35s var(--romman-ease);
}

@starting-style {
  .romman-consent-popup {
    opacity: 0;
    transform: translateY(1.25rem);
  }
}

.romman-consent-panel {
  inset-block-start: 50%;
  inset-inline-start: 50%;
  width: calc(100% - 2rem);
  max-width: 34rem;
  /* The panel scrolls internally rather than growing past the viewport: on a
     short phone screen the Save button has to stay reachable. */
  max-height: min(85vh, 44rem);
  overflow-y: auto;
  overscroll-behavior: contain;
  opacity: 1;
  /* Centring is the resting state, so it holds with or without the transition. */
  transform: translate(-50%, -50%);
  transition: opacity 0.3s var(--romman-ease), transform 0.3s var(--romman-ease);
}

/* translate(-50%) is direction-agnostic, but inset-inline-start flips under RTL,
   so the sign of the pull has to flip with it. */
[dir="rtl"] .romman-consent-panel {
  transform: translate(50%, -50%);
}

@starting-style {
  .romman-consent-panel {
    opacity: 0;
    transform: translate(-50%, -50%) scale(0.97);
  }

  [dir="rtl"] .romman-consent-panel {
    opacity: 0;
    transform: translate(50%, -50%) scale(0.97);
  }
}

.romman-consent-panel:focus {
  outline: none;
}

/* Only while the modal panel is open — the popup leaves the page usable. */
body.romman-consent-lock {
  overflow: hidden;
}

.romman-consent-title {
  font-size: 1.15rem;
  font-weight: 800;
  color: #fdf4f5;
  margin-block-end: 0.5rem;
}

.romman-consent-text {
  font-size: 0.925rem;
  line-height: 1.6;
  color: rgba(253, 244, 245, 0.80);
  margin-block-end: 1.25rem;
}

.romman-consent-inline-link {
  color: #ff9d9d;
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 2px;
}

.romman-consent-inline-link:hover,
.romman-consent-inline-link:focus-visible {
  color: #ffffff;
}

.romman-consent-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 0.75rem;
}

/* Accept and reject are both real buttons above; this styles the third,
   genuinely secondary action — opening the preference panel. */
.romman-consent-link-btn {
  background: none;
  border: 0;
  padding: 0.375rem 0.25rem;
  font-size: 0.9rem;
  font-weight: 600;
  color: rgba(253, 244, 245, 0.86);
  text-decoration: underline;
  text-underline-offset: 3px;
}

.romman-consent-link-btn:hover,
.romman-consent-link-btn:focus-visible {
  color: #ffffff;
}

.romman-consent-panel-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
}

.romman-consent-close {
  flex: 0 0 auto;
  width: 2rem;
  height: 2rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: rgba(255, 255, 255, 0.10);
  border: 0;
  border-radius: var(--glass-radius-pill);
  color: rgba(253, 244, 245, 0.86);
  transition: background-color 0.25s var(--romman-ease), color 0.25s var(--romman-ease);
}

.romman-consent-close:hover,
.romman-consent-close:focus-visible {
  background: rgba(var(--brand-rgb), 0.45);
  color: #ffffff;
}

.romman-consent-groups {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  margin-block-end: 1.25rem;
}

.romman-consent-group {
  background: rgba(255, 255, 255, 0.07);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: var(--glass-radius-sm);
  padding: 0.875rem 1rem;
}

.romman-consent-group-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}

.romman-consent-group-title {
  font-size: 0.975rem;
  font-weight: 700;
  color: #fdf4f5;
  margin: 0;
}

.romman-consent-group-title label {
  margin: 0;
  cursor: pointer;
}

.romman-consent-group-text {
  font-size: 0.85rem;
  line-height: 1.55;
  color: rgba(253, 244, 245, 0.74);
  margin: 0.375rem 0 0;
}

.romman-consent-locked {
  font-size: 0.775rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: rgba(253, 244, 245, 0.88);
  background: rgba(255, 255, 255, 0.14);
  border-radius: var(--glass-radius-pill);
  padding: 0.2rem 0.6rem;
  white-space: nowrap;
}

/* Bootstrap's switch inherits a left indent from .form-check for a label that
   lives inside it. The label sits outside here, so that indent would only push
   the switch off the row. */
.romman-consent-switch {
  flex: 0 0 auto;
  min-height: 0;
  margin: 0;
  padding-inline-start: 0;
}

.romman-consent-switch .form-check-input {
  width: 2.5rem;
  height: 1.35rem;
  margin: 0;
  cursor: pointer;
  float: none;
}

/* Bootstrap's off-state thumb is a dark circle, drawn for a pale track. On a
   translucent-white track over dark glass it disappears, and an off switch you
   cannot see is an off switch the visitor cannot trust — so the thumb SVG is
   re-declared in white. Same geometry as Bootstrap's, only the fill changes. */
.romman-consent-switch .form-check-input:not(:checked) {
  background-color: rgba(255, 255, 255, 0.14);
  border-color: rgba(255, 255, 255, 0.34);
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%28255,255,255,0.85%29'/%3e%3c/svg%3e");
}

.romman-consent-switch .form-check-input:checked {
  background-color: var(--brand);
  border-color: var(--brand);
}

.romman-consent-switch .form-check-input:focus {
  border-color: var(--brand);
  box-shadow: 0 0 0 0.2rem rgba(var(--brand-rgb), 0.35);
}

.romman-consent-error {
  font-size: 0.875rem;
  font-weight: 600;
  color: #ffc2c2;
  background: rgba(var(--brand-rgb), 0.22);
  border: 1px solid rgba(var(--brand-rgb), 0.55);
  border-radius: var(--glass-radius-sm);
  padding: 0.75rem 1rem;
  margin-block-end: 1rem;
}

/* The footer's withdrawal control. A <button>, because it opens a dialog rather
   than navigating — styled to sit level with its sibling links. */
.romman-footer-nav-btn {
  background: none;
  border: 0;
  padding: 0;
  color: #2a3547;
  font-weight: 600;
  font-size: 0.95rem;
  width: fit-content;
  transition: color 0.25s var(--romman-ease);
}

.romman-footer-nav-btn:hover,
.romman-footer-nav-btn:focus-visible {
  color: var(--brand);
}

/* The consent buttons state their own colours instead of inheriting whatever the
   page variant hands out, because that inheritance is not consistent:
   .btn-primary is only re-tinted to brand red under .romman-inner-page, so on the
   homepage (.romman-home) it fell through to the Modernize theme's blue #5D87FF —
   the same popup showed a blue Accept on one page and a red one on the next. And
   .romman-immersive gives .btn-outline-primary a white label, which vanished on
   the popup. Pinning both here makes the popup identical on every page.
   Both selector forms are listed so these outrank the (0,3,0) variant rules. */
.romman-consent-popup .btn-primary,
.romman-consent-panel .btn-primary,
.romman-immersive .romman-consent-popup .btn-primary,
.romman-immersive .romman-consent-panel .btn-primary {
  --bs-btn-color: #ffffff;
  --bs-btn-bg: var(--brand);
  --bs-btn-border-color: var(--brand);
  background-color: var(--brand);
  background-image: var(--brand-gradient);
  border-color: transparent;
  color: #ffffff;
}

/* Refuse is a light outline rather than a red one: brand red measures ~2.9:1
   against the dark surface, so a red-on-dark outline would make refusing the
   harder option to read — the exact asymmetry the popup is designed to avoid. */
.romman-consent-popup .btn-outline-primary,
.romman-consent-panel .btn-outline-primary,
.romman-immersive .romman-consent-popup .btn-outline-primary,
.romman-immersive .romman-consent-panel .btn-outline-primary {
  --bs-btn-color: #fdf4f5;
  --bs-btn-bg: transparent;
  --bs-btn-border-color: rgba(255, 255, 255, 0.48);
  background: transparent;
  border-color: rgba(255, 255, 255, 0.48);
  color: #fdf4f5;
}

/* ---- Cookie policy page ------------------------------------------------- */

/* Bootstrap gives every cell an opaque white fill via --bs-table-bg, which lands
   on top of the dark glass card and takes the immersive light text with it —
   near-white on white. Transparent cells let the table read as part of the card
   it sits in, on either surface. */
.romman-cookie-table {
  --bs-table-bg: transparent;
  --bs-table-color: inherit;
  --bs-table-border-color: rgba(0, 0, 0, 0.08);
}

.romman-cookie-table th,
.romman-cookie-table td {
  vertical-align: top;
  font-size: 0.9rem;
  background: transparent;
}

/* Colours are stated here rather than borrowed from .text-muted, so the table
   does not depend on which page-level variant happens to be active. */
.romman-cookie-table thead th {
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  white-space: nowrap;
  color: #46536b;
}

.romman-cookie-table tbody td {
  color: #46536b;
}

.romman-immersive .romman-cookie-table {
  --bs-table-border-color: rgba(255, 255, 255, 0.16);
}

.romman-immersive .romman-cookie-table thead th {
  color: rgba(255, 255, 255, 0.62);
}

.romman-immersive .romman-cookie-table tbody td {
  color: rgba(255, 255, 255, 0.82);
}

.romman-cookie-name {
  font-size: 0.85rem;
  color: var(--brand);
  background: rgba(var(--brand-rgb), 0.08);
  border-radius: 6px;
  padding: 0.15rem 0.4rem;
  word-break: break-all;
}

/* Brand red on a 8%-red wash disappears against a dark card — lift both. */
.romman-immersive .romman-cookie-name {
  color: #ffb3b3;
  background: rgba(var(--brand-rgb), 0.28);
}

/* Its own pill rather than Bootstrap's .text-bg-light-primary, which resolves to
   a light label on a pale blue fill under .romman-immersive and reads as empty. */
.romman-cookie-cat {
  display: inline-block;
  font-size: 0.775rem;
  font-weight: 700;
  white-space: nowrap;
  border-radius: var(--glass-radius-pill);
  padding: 0.2rem 0.65rem;
  color: #46536b;
  background: rgba(0, 0, 0, 0.06);
}

/* A *darker* fill, not a lighter one. The pill is small bold text, so AA still
   wants 4.5:1, and lifting a white-translucent background brightens the fill
   faster than the label gains — pushing the two together instead of apart. */
.romman-immersive .romman-cookie-cat {
  color: #ffffff;
  background: rgba(0, 0, 0, 0.28);
}

.romman-consent-summary {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.romman-consent-summary li {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  font-size: 0.9rem;
}

.romman-consent-summary li strong {
  margin-inline-start: auto;
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.romman-consent-summary li.is-on i {
  color: #13deb9;
}

.romman-consent-summary li.is-off i {
  color: var(--brand);
}

@media (max-width: 575.98px) {
  .romman-consent-popup,
  .romman-consent-panel {
    padding: 1.25rem;
  }

  /* Stacked full-width buttons on a narrow screen. Keeping accept and reject
     the same width is the point — a reject squeezed onto one line beside a
     full-width accept is exactly the imbalance regulators object to. */
  .romman-consent-actions .btn {
    flex: 1 1 100%;
  }

  .romman-consent-link-btn {
    flex: 1 1 100%;
    text-align: center;
  }
}

/* Killing the transition leaves each element at its declared state: opaque, and
   still centred. Nothing to restore. */
@media (prefers-reduced-motion: reduce) {
  .romman-consent-scrim,
  .romman-consent-popup,
  .romman-consent-panel {
    transition: none;
  }
}

/* ==========================================================================
   Homepage — stacked (single-column) layout
   Below 992px every band drops to one column: the copy sits above the artwork
   instead of beside it, so a centred block reads better than a ragged edge
   under a full-width image.

   Declared at the end of the file on purpose. The band rules further up carry
   the same specificity, so source order is what decides these — a copy of this
   block placed with the other breakpoint rules would silently lose.
   ========================================================================== */
@media (max-width: 991.98px) {
  .romman-riot-hero .col-xl-7{
    text-align: center;
  }

  .romman-cta-row {
    justify-content: center;
  }

  .romman-hero-stats .romman-stat-block {
    text-align: center;
  }

  /* The desktop measures exist to hold copy clear of the artwork beside it.
     Stacked there is nothing to clear — and the applications band's 27vw cap
     collapsed its copy to a 101px ribbon on a phone. The RTL selectors are
     repeated because the originals are direction-scoped and outrank a plain
     one. */
  .romman-riot-hero .romman-riot-copy{
    max-width: 34rem;
    margin-inline: auto;
  }

  /* Centred text wants a centred rule above it, not one hugging the edge. */
  .romman-riot-hero .romman-riot-title {
    margin-inline: auto;
  }
}

/* --- Footer, stacked ------------------------------------------------------
   The base row wraps to a column at this width, so the nav, copyright, logo,
   socials and badges all centre on their own lines rather than each hugging a
   different edge. */
@media (max-width: 991.98px) {
  .romman-footer-base {
    justify-content: center;
    text-align: center;
  }

  .romman-footer-base-text {
    flex: 1 1 100%;
  }

  .romman-footer-nav,
  .romman-store-badges,
  .romman-footer-legal .d-flex {
    justify-content: center;
  }

  /* Overrides the auto margin that pins the badges to the far edge on desktop. */
  .romman-store-badges {
    margin-inline: auto;
  }

  .romman-footer-legal,
  .romman-footer-brand {
    text-align: center !important;
  }

  .romman-footer-brand .romman-footer-logo {
    margin-inline: auto;
  }

  .romman-footer-legal p {
    max-width: none;
  }

  .romman-footer-nav a {
    /* width: fit-content on the anchors defeats text-align on the parent. */
    width: auto;
  }
}

/* The white-keyline fix that used to live here is gone with Tabler. It set
   `-webkit-text-stroke-color: currentColor` on `.ti` to hide the theme's white
   0.5px glyph outline; we now zero that stroke's WIDTH on `.ph-thin` at the top
   of this file instead, which removes the keyline and the extra weight both. */

/* ==========================================================================
   Honeypot field
   The decoy input on the contact and job-application forms. Moved off-canvas
   rather than `display: none` or `visibility: hidden`: form-filling bots
   commonly skip fields the browser reports as hidden, and a honeypot nothing
   fills catches nothing.

   Kept out of flow entirely (position: absolute) so it cannot open a gap in
   the Bootstrap row it sits in, and !important because the theme's .form-label
   / input rules would otherwise pull it back into view. The offset is `left`
   in both directions on purpose — it is off-canvas either way, and a logical
   property would put it on-screen at the right edge under RTL.
   ========================================================================== */
.romman-honeypot {
  position: absolute !important;
  left: -9999px !important;
  top: auto;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* ==========================================================================
   HOMEPAGE — the living layer
   Added with the 2026-08 restructure. The page had a video hero, a truck and a
   careers cast, and never actually showed an app; these sections put real
   store art on the page and give the dark bands something that moves.

   Everything here is decorative and every animation is switched off in the
   reduced-motion block at the bottom of this section — check there before
   adding a new moving part.
   ========================================================================== */

/* --- Content sits above the sitewide backdrop -------------------------------
   The backdrop layers (.romman-live-bg and .romman-wind, declared once in
   layouts/app.blade.php) are fixed and positioned, so without this they paint
   ABOVE the in-flow content of any section that is not itself positioned —
   which is most of an inner page. Lifting the two content wrappers into the
   positioned layer puts every page's copy back on top of the air, whatever the
   individual sections do.

   z-index 1, deliberately low: .header is 1100 and the consent bar sits at
   body level, so neither is captured by the stacking context this creates. */
.body-wrapper,
.footer-part {
  position: relative;
  z-index: 1;
}

/* --- Shared live backdrop --------------------------------------------------
   Three slow crimson/violet blooms drifting behind the whole page. `fixed`, so
   they move relative to the viewport rather than scrolling away, and they sit
   at z-index 0 with every band above them. romman-motion.js REMOVES this node
   under reduced-motion rather than merely pausing it — a static blur costs the
   compositor the same as a moving one. */
.romman-live-bg {
  position: fixed;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  /* Its own layer: without this the blurred blooms re-rasterise against the
     page on every scroll frame. */
  will-change: transform;
  contain: strict;
}

.romman-live-bg-blob {
  position: absolute;
  display: block;
  border-radius: 50%;
  /* A blur this wide is the whole effect — the blobs are never meant to read
     as shapes, only as coloured air. */
  filter: blur(90px);
  opacity: 0.5;
}

.romman-live-bg-blob--a {
  inset-inline-start: -10%;
  top: 4%;
  width: 46vw;
  height: 46vw;
  background: radial-gradient(circle, rgba(var(--brand-rgb), 0.55), transparent 68%);
  animation: romman-live-drift-a 34s ease-in-out infinite;
}

.romman-live-bg-blob--b {
  inset-inline-end: -12%;
  top: 32%;
  width: 40vw;
  height: 40vw;
  background: radial-gradient(circle, rgba(110, 137, 255, 0.4), transparent 68%);
  animation: romman-live-drift-b 44s ease-in-out infinite;
}

.romman-live-bg-blob--c {
  inset-inline-start: 28%;
  bottom: -14%;
  width: 44vw;
  height: 44vw;
  background: radial-gradient(circle, rgba(143, 22, 63, 0.5), transparent 70%);
  animation: romman-live-drift-c 52s ease-in-out infinite;
}

@keyframes romman-live-drift-a {
  0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
  50%      { transform: translate3d(6vw, 8vh, 0) scale(1.15); }
}

@keyframes romman-live-drift-b {
  0%, 100% { transform: translate3d(0, 0, 0) scale(1.1); }
  50%      { transform: translate3d(-7vw, -6vh, 0) scale(1); }
}

@keyframes romman-live-drift-c {
  0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
  50%      { transform: translate3d(-5vw, -9vh, 0) scale(1.2); }
}

/* Each band paints its own gradient over the backdrop; isolating them keeps a
   band's blend/stacking from reaching the fixed layer behind it. */
.romman-riot-hero,
.romman-metrics-band,
.romman-rail-band,
.romman-showcase{
  isolation: isolate;
}


/* --- Metrics ---------------------------------------------------------------
   The three figures that used to sit under the hero buttons. They get their own
   band so the hero can give that space to the wall. */
.romman-metrics-band {
  position: relative;
  z-index: 3;
  padding-block: 5.5rem;
  /* No background and no framing borders: the sections are transparent now so
     the wind and the brand blooms run through the whole page uninterrupted. */
  background: transparent;
}

.romman-metric strong {
  display: block;
  color: #ffffff;
  font-size: clamp(2.6rem, 5vw, 4.2rem);
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.03em;
  /* Tabular figures: without them the number visibly reflows on every tick of
     the count-up as glyph widths change. */
  font-variant-numeric: tabular-nums;
  text-shadow: 0 0 42px rgba(var(--brand-rgb), 0.5);
  /* Same trailing-plus problem as .romman-about-stat, and it has to be solved
     in CSS here: the count-up in romman-motion.js replaces this element's
     textContent, so any wrapper element would be thrown away mid-animation. */
  unicode-bidi: plaintext;
}

.romman-metric span {
  display: block;
  margin-top: 0.6rem;
  color: rgba(253, 244, 245, 0.62);
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

/* --- Rail: the portfolio marquee ------------------------------------------
   Two rows of real app icons running in opposite directions. Unlike the
   decorative icon field further down the page, these are full-colour, full-size
   and are real links. */
.romman-rail-band {
  position: relative;
  z-index: 3;
  padding-block: 6rem;
  overflow: hidden;
  background: transparent;
}

.romman-rail-band .romman-section-title {
  color: #ffffff;
}

.romman-rail-band .romman-section-copy,
html[dir="rtl"] .romman-rail-band .romman-section-copy {
  max-width: 44rem;
  color: rgba(253, 244, 245, 0.7);
}

.romman-rail {
  /* Its own clip: the track runs far wider than the viewport. */
  overflow: hidden;
  padding-block: 0.9rem;
  /* Frayed ends, so the row reads as endless rather than as a list that stops
     at the screen edge. */
  -webkit-mask-image: linear-gradient(90deg, transparent 0%, #000 8%, #000 92%, transparent 100%);
  mask-image: linear-gradient(90deg, transparent 0%, #000 8%, #000 92%, transparent 100%);
}

.romman-rail-track {
  display: flex;
  width: max-content;
  animation: romman-rail-scroll var(--romman-rail-dur, 54s) linear infinite;
  animation-direction: var(--romman-rail-dir, normal);
  will-change: transform;
}

.romman-rail--rev { --romman-rail-dir: reverse; --romman-rail-dur: 68s; }

/* RTL needs the travel MIRRORED, not merely reversed — and the difference is
   the whole bug. Under `direction: rtl` a `width: max-content` flex track is
   laid out from the container's RIGHT edge and overflows to the LEFT, so at
   translate 0 the visible window already sits at the track's right end.
   Translating another -50% from there moves the entire track past the left
   edge and the row renders EMPTY (measured: 0 of 24 cards visible).
   Flipping the sign is what mirrors it; `animation-direction` stays as it is,
   because reversing that as well would cancel the mirror out. */
html[dir="rtl"] .romman-rail-track {
  animation-name: romman-rail-scroll-rtl;
}

.romman-rail:hover .romman-rail-track,
.romman-rail:focus-within .romman-rail-track {
  animation-play-state: paused;
}

.romman-rail-tile {
  display: flex;
  gap: 1.1rem;
  padding-inline-end: 1.1rem;
}

/* Same -50% rule as the wall: two identical tiles, travel exactly one. */
@keyframes romman-rail-scroll {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-50%, 0, 0); }
}

/* The mirror image of the above, for RTL. Two keyframe sets rather than a
   var() endpoint: the sign has to be a real declared value, and a custom
   property read inside @keyframes is the kind of thing that works until a
   browser decides otherwise. */
@keyframes romman-rail-scroll-rtl {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(50%, 0, 0); }
}

.romman-rail-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.75rem;
  width: 132px;
  padding: 1rem 0.6rem;
  border-radius: 20px;
  border: 1px solid rgba(255, 255, 255, 0.07);
  background: rgba(255, 255, 255, 0.03);
  color: rgba(253, 244, 245, 0.72);
  text-decoration: none;
  transition: transform 0.25s ease, border-color 0.25s ease, background-color 0.25s ease, box-shadow 0.25s ease;
}

.romman-rail-card:hover,
.romman-rail-card:focus-visible {
  transform: translateY(-6px);
  border-color: rgba(var(--brand-blend-rgb), 0.55);
  background: rgba(var(--brand-rgb), 0.1);
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.5), 0 0 34px rgba(var(--brand-rgb), 0.35);
  color: #ffffff;
}

.romman-rail-icon {
  width: 72px;
  height: 72px;
  /* Matches the platform icon mask everywhere else on the site. */
  border-radius: 24%;
  object-fit: cover;
  /* Several of our icons are artwork on a near-black square (Steps, Kids
     Police). On a dark card those bleed into the card and read as a missing
     image, so every icon gets its own hairline edge to sit inside. */
  background: rgba(255, 255, 255, 0.05);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.1);
}

.romman-rail-name {
  font-size: 0.78rem;
  font-weight: 600;
  line-height: 1.3;
  text-align: center;
  /* Two lines then ellipsis: Arabic app names are long and a third line would
     make the cards different heights. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* --- Showcase: one app at a time ------------------------------------------
   The section is N screens tall and its inner stage sticks, so scrolling
   through it swaps art and copy in place. */
.romman-showcase {
  z-index: 3;
  min-height: calc(var(--romman-showcase-steps, 3) * 100vh);
  background: transparent;
}

.romman-showcase-sticky {
  position: sticky;
  /* 0, not the header height: this box now carries a full-bleed background, and
     an 88px offset left a strip of bare section colour above it. The header is
     cleared with padding instead, so the artwork runs under it. */
  top: 0;
  display: flex;
  align-items: center;
  min-height: 100vh;
  padding-block: calc(var(--romman-header-h, 88px) + 3rem) 3rem;
  /* Contains the absolutely-positioned background layers. */
  overflow: hidden;
}

/* Panels share one grid cell so the column is as tall as the tallest of them
   and nothing shifts as they cross-fade. */
.romman-showcase-panels {
  display: grid;
}

.romman-showcase-panel {
  grid-area: 1 / 1;
  opacity: 0;
  transform: translateY(18px);
  transition: opacity 0.45s ease, transform 0.45s ease;
  /* Not display:none — the panel has to keep contributing its height to the
     grid cell. `inert` in the markup is what takes it out of the tab order. */
  pointer-events: none;
}

.romman-showcase-panel.is-active {
  opacity: 1;
  transform: none;
  pointer-events: auto;
}

/* No backdrop-filter on anything inside a panel that cross-fades.

   The panels animate opacity, which makes each one a BACKDROP ROOT while it is
   mid-fade — a glass child then samples that isolated group instead of the page
   and flashes as a pale rectangle for the length of the transition. The ghost
   button is the only glass element in here; it keeps its translucent fill and
   simply stops trying to blur what is behind it. */
.romman-showcase-panel .romman-section-title {
  color: #ffffff;
}

.romman-showcase-panel .romman-section-copy,
html[dir="rtl"] .romman-showcase-panel .romman-section-copy {
  max-width: 34rem;
  color: rgba(253, 244, 245, 0.72);
}

.romman-showcase-dots {
  display: flex;
  gap: 0.5rem;
  margin-top: 2.5rem;
}

.romman-showcase-dot {
  width: 30px;
  height: 4px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.18);
  transition: background-color 0.35s ease, width 0.35s ease;
}

.romman-showcase-dot.is-active {
  width: 52px;
  background: var(--brand);
}

/* ---- Active indicator gets the fill crop --------------------------------
   All three reels (homepage showcase, jobs carousel, values roll) share this.

   It arrives as an OVERLAY rather than as a background-image on the bar itself,
   because background-image does not interpolate: setting the gradient directly
   would snap the colour in while the width was still animating over 0.35s, and
   the bar's own `transition: background-color` would go dead. This way the bar
   keeps its white-ghost -> brand-red fade and the ramp fades in on top of it
   over the same duration.

   No `overflow: hidden` to clip the overlay -- on mobile these same dots grow a
   ::before thumb target at `inset: -14px -4px`, and clipping the box would throw
   that tap area away. `border-radius: inherit` rounds the overlay instead.

   A 4px bar is too short for a diagonal to register, so the overlay runs the
   ramp along the bar (`to right`, flipped in RTL) where its 30-52px of length
   actually is. */
.romman-showcase-dot,
.romman-jobs-dot {
  position: relative;
}

.romman-showcase-dot::after,
.romman-jobs-dot::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  /* not the shared token: see the note above on bar length vs. diagonal */
  background-image: linear-gradient(to right, var(--brand) 0%, var(--font-gradient-second) 55%, var(--brand-strong) 100%);
  opacity: 0;
  transition: opacity 0.35s ease;
  /* the dots are clickable; the overlay must not intercept the tap */
  pointer-events: none;
}

html[dir="rtl"] .romman-showcase-dot::after,
html[dir="rtl"] .romman-jobs-dot::after {
  background-image: linear-gradient(to left, var(--brand) 0%, var(--font-gradient-second) 55%, var(--brand-strong) 100%);
}

.romman-showcase-dot.is-active::after,
.romman-jobs-dot.is-active::after {
  opacity: 1;
}


@media (max-width: 991.98px) {

  /* The scrim is a horizontal gradient built for a wide hero where the copy
     sits on one side. On a phone there IS no other side — the dark end covers
     the whole width and the hero goes back to being the black rectangle this
     was all meant to fix. Vertical instead: art at the top and bottom, dark
     through the middle where the text actually sits. */
  .romman-riot-hero-overlay,
  html[dir="rtl"] .romman-riot-hero-overlay {
    background: linear-gradient(
      180deg,
      rgba(0, 0, 0, 0.32) 0%,
      rgba(0, 0, 0, 0.88) 22%,
      rgba(0, 0, 0, 0.9) 72%,
      rgba(0, 0, 0, 0.45) 100%
    );
  }

  .romman-metrics-band {
    padding-block: 3.5rem;
  }

  /* Roomier than the desktop rhythm, not tighter: stacked on a phone these
     three sections ran into each other, with How we work / Featured Apps / the
     portfolio reading as one long column rather than three bands. */
  .romman-rail-band {
    padding-block: 5.5rem;
  }

  .romman-rail-card {
    width: 108px;
  }

  .romman-rail-icon {
    width: 58px;
    height: 58px;
  }

  /* SWIPE, not scroll. The section used to be three viewports tall with its
     stage stuck, so moving through three apps cost three screens of vertical
     scrolling — which on a phone reads as the page having stopped responding.
     Sideways is the gesture that means "next" here, so the stage becomes a
     horizontal snap rail and the section is one screen tall again.

     `min-height` goes with it: it existed only to buy the scroll travel, and
     there is nothing left to travel through. */
  .romman-showcase {
    min-height: 0;
  }

  .romman-showcase-sticky {
    /* Relative, not static: the full-bleed background layers are positioned
       against this box, so it has to stay their containing block. */
    position: relative;
    /* `block`, not the desktop flex row: the art is IN FLOW here and has to
       stack above the copy rather than sit behind it. */
    display: block;
    min-height: 0;
    /* No top padding — the art starts at the section's edge; the gap above it is
       carried by the values band's bottom padding instead. */
    padding-block: 0 6rem;
  }

  /* The art itself is moved into flow further down the file — it HAS to be
     declared after `.romman-showcase-bg` and `.romman-showcase-bg-layer--art`,
     which are below this block. See "Showcase art on top" there. */

  /* Centred, to match How we work directly above it. */
  .romman-showcase-copy {
    text-align: center;
  }

  /* The measure is set on the desktop rule and direction-scoped, so both
     selectors have to be repeated to re-centre it. */
  .romman-showcase-panel .romman-section-copy,
  html[dir="rtl"] .romman-showcase-panel .romman-section-copy {
    margin-inline: auto;
  }

  /* The rail. `scroll-snap-stop: always` on the cards keeps a hard flick from
     skipping the middle app, and `overscroll-behavior-x: contain` stops a swipe
     past the last one from triggering the browser's back gesture.

     Unlike the jobs pager further down, `overflow-x: auto` is the right tool
     here: that one was button-driven and its faults were a clipped hover shadow
     and a half-shown card mid-transition. On a rail the visitor drags with a
     finger, so the intermediate positions ARE the interaction. */
  .romman-showcase-panels {
    display: flex;
    gap: 1rem;
    overflow-x: auto;
    overflow-y: hidden;
    /* Room for a focus ring, which `overflow-y: hidden` would otherwise clip. */
    padding-block: 4px;
    scroll-snap-type: x mandatory;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
  }

  .romman-showcase-panels::-webkit-scrollbar {
    display: none;
  }

  /* A FULL rail width each, so the centred card is genuinely centred.

     This was `calc(100% - 3rem)`, short of the rail so the next card's edge
     peeked in as a swipe hint. The hint cost more than it bought: a card
     narrower than the rail cannot centre at the ends of the scroll range, so the
     first and last slides clamped flush and sat 24px off the viewport's centre
     line — which read, correctly, as "this content is not centred". The dots
     carry the affordance instead.

     The cross-fade comes off since every card is visible in turn, but the driver
     still marks one active, which is what drives the background and the dots.
     `pointer-events` is left alone deliberately: with only the centred card
     interactive, a stray tap during a drag falls through to the rail and scrolls
     it rather than half-hitting a store button. */
  /* Fully opaque, and NO transition — the slide is the animation.

     These used to dim to 0.32 and ramp back up when the driver flipped the active
     card at the drag's midpoint. That existed to stop the peeking sliver of the
     next card reading as clipped text, and the peek is gone. What was left was a
     brightness ramp on the slide moving INTO view: it arrived dark and then lit
     up, which read as the carousel sticking half way and then catching up. */
  .romman-showcase-panel,
  .romman-showcase-panel.is-active {
    flex: 0 0 100%;
    opacity: 1;
    transform: none;
    transition: none;
    scroll-snap-align: center;
    scroll-snap-stop: always;
  }

  .romman-showcase-panel .romman-section-title {
    font-size: clamp(1.7rem, 7.5vw, 2.4rem);
  }

  /* Centred here, start-aligned on desktop where it sits under left-aligned
     copy. The dots were 119px left of the screen's centre line. */
  .romman-showcase-dots {
    justify-content: center;
    margin-top: 1.75rem;
  }

  /* The store buttons become their logos, so all three CTAs fit on ONE row.
     With the theme's `min-width: 180px` on every .btn they could only ever
     stack — 180px each in a 318px row — which cost three rows of height inside
     a card that has to fit on a phone screen. */
  .romman-showcase-panel .romman-cta-row .btn {
    min-width: 0;
  }

  /* The primary keeps its words and takes whatever the logos leave. */
  .romman-showcase-panel .romman-cta-row .romman-btn-solid {
    flex: 1 1 auto;
  }

  /* Square, logo only. `inline-flex` because the theme's .btn computes to
     `display: block`, which will not centre the glyph on both axes. */
  .romman-showcase-panel .romman-btn-ghost {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 3.25rem;
    padding-inline: 0;
  }

  .romman-showcase-panel .romman-btn-ghost i {
    font-size: 1.35rem;
    line-height: 1;
  }

  /* Hidden from EYES only. The label is still the link's accessible name, so a
     screen reader announces "App Store" rather than an unlabelled link — which
     is what `display: none` or dropping the text would have cost. */
  .romman-showcase-panel .romman-store-label {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }

}

/* --- Homepage reduced motion ----------------------------------------------
   Every moving part added by the restructure stops here. The wall, rail and
   field keep their PATTERN (they are composition, not decoration) — only the
   travel stops. */
@media (prefers-reduced-motion: reduce) {
  .romman-rail-track,
  .romman-live-bg-blob {
    animation: none !important;
  }

  .romman-showcase-panel,
  .romman-showcase-dot,
  .romman-rail-card {
    transition: none !important;
  }

  /* Sticky scroll-swapping is motion the visitor did not ask for, so the
     showcase falls back to the stacked layout the small breakpoint uses. */
  .romman-showcase {
    min-height: 0;
  }

  .romman-showcase-sticky {
    position: static;
    min-height: 0;
  }

  .romman-showcase-panels {
    display: block;
  }

  .romman-showcase-panel {
    opacity: 1;
    transform: none;
    pointer-events: auto;
  }

  .romman-showcase-panel + .romman-showcase-panel {
    margin-top: 3rem;
  }
}

/* ==========================================================================
   GENERATED KEY ART placements
   Art produced from docs/art-direction-prompt.md. All of it is authored on a
   pure-black ground, which is why none of it needs a cut-out or a blend mode:
   it composites straight onto the black canvas. The one exception is the
   mascot, which was keyed off a chroma-green plate to a transparent PNG.
   ========================================================================== */

/* --- Metrics band: the wisp backdrop + the mascot -------------------------- */
/* Padding only for the band; the panel below does the work.

   The DEFAULT is no overlap: the panel sits below the hero, which is safe at any
   viewport. The lift that pulls it up onto the hero's bottom edge — so the
   figures attach to the claim they are evidence for instead of floating in their
   own band — is opted into further down, where there is room for it. */
.romman-metrics-band {
  margin-top: 0;
  /* 4rem, not 2.5: on a short viewport the hero's copy overflows its own 100vh
     and the CTA row ends up past the section's bottom edge, which left the
     buttons and the panel 8px apart. Measured at 1440x700. */
  padding-block: 4rem 5rem;
}

/* --- The metrics panel -----------------------------------------------------
   Glass, following the same idiom as the job tiles rather than the --glass-*
   tokens, which are authored for the light theme. `backdrop-filter` is the
   point: the wind keeps moving behind the numbers and reads through the pane,
   so the section is alive without needing an animation of its own. */
.romman-metrics-panel {
  display: grid;
  /* minmax(0, 1fr) rather than 1fr: a long label should wrap, not push the
     column past its share and overflow the pane. */
  grid-template-columns: repeat(3, minmax(0, 1fr));
  /* No gap — the hairlines below sit on the cell edge, so a gap would leave
     each line closer to the figure after it than the one before it. Each
     .romman-metric pads itself instead, which keeps the spacing symmetrical. */
  gap: 0;
  /* Capped and pinned to the inline start rather than spanning the container.
     At full width each figure got a ~440px cell to sit alone in, which is what
     made the three read as unrelated objects adrift in the black instead of one
     line of evidence — and it left the group with nothing to belong to. At this
     measure it lines up under the hero's headline and buttons (the copy column
     starts at the same x, since both are laid out off the container's content
     box), so it reads as the close of that argument rather than a floating
     pane. Logical margin, so Arabic pins it to the right along with everything
     else. Swap to `margin-inline: auto` to centre it again. */
  margin-inline: 0 auto;
  padding: 2.25rem 1.75rem;
  border-radius: 26px;
  border: 1px solid rgba(255, 255, 255, 0.09);
  background: rgba(255, 255, 255, 0.04);
  -webkit-backdrop-filter: blur(var(--glass-blur, 20px)) saturate(160%);
  backdrop-filter: blur(var(--glass-blur, 20px)) saturate(160%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.14),
    /* Shallower than it was: a 60px drop under a pane with nothing beneath it
       is exactly what "floating" looks like. This seats it. */
    0 14px 34px rgba(0, 0, 0, 0.38);
}

/* Hairlines between the figures instead of three separate cards — one pane
   reads as a single statement, three would read as another tile grid. */
.romman-metrics-panel .romman-metric + .romman-metric {
  border-inline-start: 1px solid rgba(255, 255, 255, 0.08);
}

.romman-metric {
  text-align: center;
  padding-inline: 1rem;
}

/* --- The plate, cut into three ----------------------------------------------
   .is-assembling is put on the pane by the metrics module in romman-motion.js
   for the length of the flight, and taken off the moment the last piece seats.

   The plate is not decorated while it is in pieces — it is DISASSEMBLED. Its
   skin comes off the pane and goes onto the three figures, one third each, so
   what flies in is three pieces OF the plate rather than three tiles on top of
   it. Butted back together they ARE the plate: same fill, same 1px edge, same
   glass, and only the outer corners rounded — so the joins stay square and read
   as the two hairlines that were always there.

   Two details do the work:

     - Each piece BLEEDS out to where the plate's edge was, with a negative
       margin of the plate's padding PLUS its 1px border. Without it the pieces
       would only cover the plate's content box, and the plate they reassemble
       into would come out smaller than the one they replaced.
     - The padding that puts the figure back is the plate's padding ONLY — the
       piece's own 1px border accounts for the rest. Adding the border into the
       padding as well moves every figure inward by a pixel, and a pixel of
       glyph shift is the single biggest term in the diff against the real
       plate: far bigger than any difference in the glass.

   Checked by pixel-diffing the reassembled plate against the real one, not by
   eye: a 1px seam or a doubled shadow is invisible in a screenshot and obvious
   on a monitor. */
.romman-metrics-panel.is-assembling {
  /* Off, all of it — including the glass. A plate still painting its own fill
     while the pieces fly toward it is the "second block background": you would
     see the finished plate, and three tiles arriving on top of it. */
  border-color: transparent;
  background: transparent;
  /* Including the shadow: a plate that does not exist yet does not cast one,
     and on this background a 34px shadow with no fill inside it still reads as
     a soft outline of the finished plate. The pieces carry it instead. */
  box-shadow: none;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

.romman-metrics-panel.is-assembling .romman-metric {
  /* The plate's own padding. The 1px of its border is added to the MARGIN only,
     never to the padding. */
  --romman-plate-inline: 1.75rem;
  --romman-plate-block: 2.25rem;
  margin-block: calc(-1 * (var(--romman-plate-block) + 1px));
  padding-block: var(--romman-plate-block);
  border: 1px solid rgba(255, 255, 255, 0.09);
  border-radius: 0;
  background: rgba(255, 255, 255, 0.04);
  -webkit-backdrop-filter: blur(var(--glass-blur, 20px)) saturate(160%);
  backdrop-filter: blur(var(--glass-blur, 20px)) saturate(160%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.14),
    0 14px 34px rgba(0, 0, 0, 0.38);
}

/* The joins are the two hairlines, so they are drawn in the hairline's colour
   and not the outer edge's. One 100th of alpha apart, but it is free to get
   right and it is what the pixel diff is measuring at the seams. */
.romman-metrics-panel.is-assembling .romman-metric:not(:first-child) {
  border-inline-start-color: rgba(255, 255, 255, 0.08);
}

/* One 1px line per join, not two. Every piece keeps its inline-start edge and
   drops its inline-end, so a join is drawn once — by the piece after it — and
   lands exactly where the hairline was. */
.romman-metrics-panel.is-assembling .romman-metric:not(:last-child) {
  border-inline-end: 0;
}

/* Only the plate's OUTER corners are round. Rounding the joins as well is what
   would turn three pieces of one plate back into three separate tiles. */
.romman-metrics-panel.is-assembling .romman-metric:first-child {
  margin-inline-start: calc(-1 * (var(--romman-plate-inline) + 1px));
  padding-inline-start: calc(1rem + var(--romman-plate-inline));
  border-start-start-radius: 26px;
  border-end-start-radius: 26px;
}

.romman-metrics-panel.is-assembling .romman-metric:last-child {
  margin-inline-end: calc(-1 * (var(--romman-plate-inline) + 1px));
  padding-inline-end: calc(1rem + var(--romman-plate-inline));
  border-start-end-radius: 26px;
  border-end-end-radius: 26px;
}

@media (max-width: 767.98px) {
  .romman-metrics-panel {
    grid-template-columns: 1fr;
    gap: 0;
    padding: 1.75rem 1.25rem;
  }

  /* Stacked, so the divider has to move to the block edge — an inline border
     between rows in a single column draws down the side of nothing. */
  .romman-metrics-panel .romman-metric + .romman-metric {
    border-inline-start: 0;
    border-top: 1px solid rgba(255, 255, 255, 0.08);
    margin-top: 1.5rem;
    padding-top: 1.5rem;
  }

  /* --- The stacked plate, cut into three -----------------------------------
     Same idea as the row above, turned ninety degrees: the joins run across the
     plate, so every piece bleeds to BOTH side edges and the block bleed goes to
     the join rather than to the plate.

     The gap between two pieces is the next one's 1.5rem margin, so each piece
     reaches down across exactly that much — which puts the following piece's
     block-start edge, and therefore the join, precisely where the hairline was.
     Anything else and the two dividers jump when the pieces are swapped for the
     real plate. */
  .romman-metrics-panel.is-assembling .romman-metric {
    --romman-plate-inline: 1.25rem;
    --romman-plate-block: 1.75rem;
    margin-inline: calc(-1 * (var(--romman-plate-inline) + 1px));
    padding-inline: calc(1rem + var(--romman-plate-inline));
    /* Block-start is left exactly as the stacked plate has it: pieces 2 and 3
       already carry a 1px border-top and a 1.5rem padding-top there, which is
       what the hairline and its breathing room were made of. Only the reach
       DOWN across the next piece's margin is new.

       Longhands, deliberately. `margin-block: 0 -1.5rem` is a shorthand, and its
       0 wipes the margin-top the divider rule puts on pieces 2 and 3 — 24px of
       margin and 24px of padding per join, which collapsed the stack by 96px the
       moment the pieces took over. */
    margin-block-end: -1.5rem;
    padding-block-end: 1.5rem;
    border: 1px solid rgba(255, 255, 255, 0.09);
    border-radius: 0;
    /* Same skin as the row: in flight these are three separate objects and each
       gets the plate's lit top edge and its shadow. Stacked, a piece's shadow
       does fall on the piece below it once they are merged — for the one frame
       that state lasts before the real plate takes over. */
    box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, 0.14),
      0 14px 34px rgba(0, 0, 0, 0.38);
  }

  .romman-metrics-panel.is-assembling .romman-metric:not(:first-child) {
    border-inline-start-color: rgba(255, 255, 255, 0.09);
    border-block-start-color: rgba(255, 255, 255, 0.08);
  }

  .romman-metrics-panel.is-assembling .romman-metric:not(:last-child) {
    border-block-end: 0;
    border-inline-end: 1px solid rgba(255, 255, 255, 0.09);
  }

  .romman-metrics-panel.is-assembling .romman-metric:first-child {
    margin-block-start: calc(-1 * (var(--romman-plate-block) + 1px));
    padding-block-start: var(--romman-plate-block);
    margin-inline-start: calc(-1 * (var(--romman-plate-inline) + 1px));
    padding-inline-start: calc(1rem + var(--romman-plate-inline));
    border-start-start-radius: 26px;
    border-start-end-radius: 26px;
    border-end-start-radius: 0;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14);
  }

  .romman-metrics-panel.is-assembling .romman-metric:last-child {
    margin-block-end: calc(-1 * (var(--romman-plate-block) + 1px));
    padding-block-end: var(--romman-plate-block);
    margin-inline-end: calc(-1 * (var(--romman-plate-inline) + 1px));
    padding-inline-end: calc(1rem + var(--romman-plate-inline));
    border-end-start-radius: 26px;
    border-end-end-radius: 26px;
    border-start-end-radius: 0;
  }
}


/* --- The lift onto the hero's bottom edge -----------------------------------
   The band DOES pull up onto the hero now, but the amount is measured in
   romman-motion.js rather than declared here — and this is the fourth attempt,
   so why CSS cannot do it is worth keeping.

   The idea is to pull the panel up so the figures attach to the claim they are
   evidence for. Whether that is safe depends on the hero having spare room under
   its copy, and that room is not derivable from the viewport:

     - It is not a width condition. Gated on `max-width` the lift dropped the
       panel onto the "Explore our apps" / "View careers" buttons at 1440x700,
       where the hero's copy already overflows its own 100vh.
     - It is not a height condition either. Gated on `min-height: 900px` it still
       covered the buttons by 27px at 1920x900 and 2560x900, because the headline
       is clamped against vw — a wider hero has a TALLER copy block.
     - It is not a smooth function of the two. Capping the lift by `50vh` as well
       left 6px of clearance at 2560x900 and -34px at 3440x1000, and a sweep of
       9 widths x 7 heights showed why: the clearance is NON-MONOTONIC in width
       (20px at 2560x1000, -34px at 3440x1000, +16px at 3840x1000) because it
       depends on where the headline happens to wrap, which jumps.

   A discontinuous constraint cannot be tracked by a `clamp`, so nothing here
   tries. The script measures the actual distance from the hero's buttons to this
   band, subtracts --romman-metrics-gap, and writes what is left over into
   --romman-metrics-lift. It only ever CLOSES a gap: with no slack the value is
   0px and the layout is the untouched one, which is also what renders with JS
   off and before the first pass. That is what makes a short viewport safe
   without a query having to predict which viewports are short.

   --romman-metrics-gap is the knob. It is the clearance left under the buttons,
   and the script never closes past it. It has to be an ABSOLUTE length: a custom
   property is handed back by getComputedStyle as the raw token it was written
   with, not as a resolved length, so `2.5rem` reaches the script as the number
   2.5 and the gap silently collapses to 2px. Measured, not guessed — that was
   the first run of this.

   Desktop only, and the gate is this media query rather than a branch in the
   script: below 992px the hero is `min-height: auto` and shrink-wraps its
   content, so there is no dead space to reclaim, and the 3.5rem rhythm there was
   set deliberately to stop the stacked bands running into each other. Gating in
   CSS means a value the script left on the element while the window was wide
   cannot leak into the phone layout on resize. */
.romman-metrics-band {
  --romman-metrics-gap: 120px;
  --romman-metrics-lift: 0px;
}

@media (min-width: 992px) {
  .romman-metrics-band {
    margin-top: calc(-1 * var(--romman-metrics-lift, 0px));
  }
}


/* ==========================================================================
   OPEN POSITIONS
   The careers band pitches; this delivers the roles. Cards follow the same
   glass idiom as the rest of the dark sections rather than the light panel the
   reference uses — this page is dark-dominant and a white band between the red
   careers section and the footer would be a third colour fighting both.
   ========================================================================== */
.romman-jobs-band {
  /* The careers band that used to sit above this — and whose cast overhung
     onto it — has been removed, so there is no artwork to duck under any more.
     2 keeps it above the footer (1), same as that band did. */
  z-index: 2;
  position: relative;
  padding-block: 6rem 7rem;
  background: transparent;
}

.romman-jobs-band .romman-section-title {
  color: #ffffff;
}

.romman-jobs-band .romman-section-copy,
html[dir="rtl"] .romman-jobs-band .romman-section-copy {
  max-width: 40rem;
  color: rgba(253, 244, 245, 0.7);
}

.romman-job-tile {
  display: flex;
  flex-direction: column;
  /* No `height: 100%` here. These are flex items of an auto-height flex row,
     and a percentage height against a container whose own height depends on
     them resolves to `auto` — which defeats the default `align-items: stretch`
     and leaves every card sized to its own content. Measured: 311/204/311/337.
     The declaration was a leftover from the Bootstrap grid this used to sit in;
     stretch is what equalises them, and that is what pins each card's "Apply"
     button to a common baseline. */
  padding: 1.75rem;
  border-radius: 22px;
  /* Glass, same idiom as the metrics panel: a lit top edge, a slightly milkier
     fill and the -webkit- prefix, which was missing before — without it Safari
     rendered these as flat translucent boxes with no blur at all. The wind
     moving behind the page is what the pane actually has to show through. */
  border: 1px solid rgba(255, 255, 255, 0.1);
  background: rgba(255, 255, 255, 0.055);
  -webkit-backdrop-filter: blur(var(--glass-blur, 20px)) saturate(165%);
  backdrop-filter: blur(var(--glass-blur, 20px)) saturate(165%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.13),
    0 16px 36px rgba(0, 0, 0, 0.32);
  transition: transform 0.25s ease, border-color 0.25s ease, background-color 0.25s ease, box-shadow 0.25s ease;
}

/* The brand glow on hover is INSET, not an outer halo.

   An outer coloured glow has to be allowed to spread past the card, and the
   track is a scroll container that clips it — which is what produced the hard
   line down the first and last cards. Putting the light inside the card means
   there is nothing to clip, at any position in the row. The dark drop shadow
   below is kept, and stays tight: it is black on a black page, so even where
   the container does cut it, there is nothing to see. */
.romman-job-tile:hover {
  transform: translateY(-6px);
  border-color: rgba(var(--brand-blend-rgb), 0.62);
  background: rgba(var(--brand-rgb), 0.1);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.18),
    inset 0 0 30px rgba(var(--brand-rgb), 0.28),
    inset 0 -40px 60px -40px rgba(var(--brand-rgb), 0.5),
    0 16px 28px -12px rgba(0, 0, 0, 0.6);
}

.romman-job-tag {
  margin-bottom: 0.6rem;
  color: var(--brand);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.romman-job-title {
  margin-bottom: 0.9rem;
  color: #ffffff;
  font-size: 1.28rem;
  font-weight: 800;
  line-height: 1.25;
  letter-spacing: -0.01em;
}

.romman-job-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1.1rem;
  margin-bottom: 1.1rem;
  color: rgba(253, 244, 245, 0.6);
  font-size: 0.82rem;
}

.romman-job-meta span {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}

.romman-job-meta i {
  color: rgba(253, 244, 245, 0.9);
}

.romman-job-blurb {
  padding-block-end: 1.4rem;
  color: rgba(253, 244, 245, 0.62);
  font-size: 0.9rem;
  line-height: 1.6;
}

.romman-job-apply {
  /* On the BUTTON, not on the blurb above it: several roles have neither a
     summary nor responsibilities and render no blurb, and an auto margin that
     lives on the blurb disappears with it — leaving those buttons floating
     under the meta row while every other card's sat at the bottom. */
  margin-block-start: auto;
  align-self: flex-start;
  padding: 0.6rem 1.5rem;
  border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, 0.22);
  color: #ffffff;
  font-size: 0.86rem;
  font-weight: 700;
  text-decoration: none;
  transition: background-color 0.25s ease, border-color 0.25s ease;
}

.romman-job-tile:hover .romman-job-apply {
  border-color: transparent;
  background: var(--brand);
}

/* ---- The two opaque brand HOVER fills ----------------------------------
   Measured with CSS.forcePseudoState, these are the ONLY two brand hover fills
   still alive on the site: every brand hover on a .btn is dead code, because
   the shared "white face, black edge" hover outranks it (verified — .btn-primary
   and .btn-outline-primary both resolve to #ffffff on hover, not brand).

   Same cross-fade trick as the reel indicators, and for the same reason:
   background-image does not interpolate, so painting the gradient directly
   would snap it in while background-color was still fading over 0.2-0.25s.
   The overlay carries the ramp and fades its opacity instead.

   z-index: -1 with isolation: isolate, not a ::after on top: both of these have
   real content (a text label, an icon glyph) that an overlay would otherwise
   cover, and text nodes cannot be given a stacking order of their own. The
   isolation is what keeps the negative index from escaping behind the card. */
.romman-job-apply,
.romman-jobs-arrow {
  position: relative;
  isolation: isolate;
}

.romman-job-apply::before,
.romman-jobs-arrow::before {
  content: '';
  position: absolute;
  /* -1px, not 0: on hover the 1px border goes transparent while the element's
     own brand background-color still paints under it (background-clip defaults
     to border-box), so an inset:0 overlay covering only the padding box left a
     flat red ring around the gradient. This covers the border box too. */
  inset: -1px;
  z-index: -1;
  border-radius: inherit;
  background-image: var(--brand-gradient);
  opacity: 0;
  transition: opacity 0.25s ease;
  pointer-events: none;
}

.romman-job-tile:hover .romman-job-apply::before,
.romman-jobs-arrow:hover:not(:disabled)::before {
  opacity: 1;
}

@media (max-width: 991.98px) {
  .romman-jobs-band {
    padding-block: 4rem 4.5rem;
  }
}

/* --- Jobs pager -----------------------------------------------------------
   A PAGER, not a scroller. It was a scroll-snap rail, and `overflow-x: auto`
   caused both of the problems it was meant to avoid:

     * it clips, so every card's hover shadow was sliced flat wherever it met
       the container — no amount of padding fixes that, because side room wide
       enough for a glow is also room a fourth card shows through;
     * and a scroller has intermediate positions, so part of the next card is
       visible whenever it is mid-scroll, however exact the page arithmetic is.

   Rendering only the current page removes both at once: nothing overflows, so
   nothing is clipped, and there is no in-between state for a fourth card to
   appear in. --romman-jobs-per-page is the single source of truth for the page
   size — romman-motion.js reads it rather than keeping its own copy. */
.romman-jobs-carousel {
  position: relative;
  --romman-jobs-per-page: 3;
}

.romman-jobs-track {
  display: grid;
  grid-template-columns: repeat(var(--romman-jobs-per-page), minmax(0, 1fr));
  gap: 1.5rem;
  /* Room for the hover lift and its shadow. Nothing clips here any more, so
     this is only to stop neighbouring sections crowding it. */
  padding-block: 1.25rem 2rem;
}

/* Tiles not on the current page are removed from layout entirely, so they
   cannot be seen, tabbed into, or read out. */
.romman-jobs-track > .romman-job-tile[hidden] {
  display: none;
}

/* Incoming cards get a short rise. Transform only — never opacity: these tiles
   use backdrop-filter, and an ancestor (or a fading self) with opacity creates
   a backdrop root that makes the blur sample an empty group and flash pale. */
@keyframes romman-jobs-page-in {
  from { transform: translateY(14px); }
  to   { transform: translateY(0); }
}

.romman-jobs-track > .romman-job-tile.is-entering {
  animation: romman-jobs-page-in 0.42s cubic-bezier(0.22, 1, 0.36, 1) both;
}

.romman-jobs-nav {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  margin-top: 2.5rem;
}

.romman-jobs-arrow {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.18);
  background: rgba(255, 255, 255, 0.04);
  color: #ffffff;
  transition: background-color 0.2s ease, border-color 0.2s ease, opacity 0.2s ease;
}

.romman-jobs-arrow:hover:not(:disabled) {
  border-color: transparent;
  background: var(--brand);
}

.romman-jobs-arrow:disabled {
  opacity: 0.3;
  cursor: default;
}

/* The caret glyphs are physical, so RTL has to flip the pair or "next" points
   backwards against the reading direction. */
html[dir="rtl"] .romman-jobs-arrow i {
  transform: scaleX(-1);
}

.romman-jobs-dots {
  display: flex;
  gap: 0.45rem;
}

.romman-jobs-dot {
  width: 26px;
  height: 4px;
  border-radius: 999px;
  border: 0;
  padding: 0;
  background: rgba(255, 255, 255, 0.18);
  transition: background-color 0.3s ease, width 0.3s ease;
}

.romman-jobs-dot.is-active {
  width: 44px;
  background: var(--brand);
}

@media (max-width: 991.98px) {
  .romman-jobs-carousel {
    --romman-jobs-per-page: 2;
  }
}

@media (max-width: 575.98px) {
  .romman-jobs-carousel {
    --romman-jobs-per-page: 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  .romman-jobs-track > .romman-job-tile.is-entering {
    animation: none;
  }
}

/* --- The closing "see everything" tile ------------------------------------ */
.romman-job-tile--cta {
  align-items: flex-start;
  justify-content: center;
  text-align: start;
  border-color: rgba(var(--brand-blend-rgb), 0.35);
  background: rgba(var(--brand-rgb), 0.12);
}

.romman-job-tile--cta:hover {
  transform: none;
  background: rgba(var(--brand-rgb), 0.14);
}

.romman-job-cta-title {
  margin-bottom: 0.9rem;
  color: #ffffff;
  font-size: 1.35rem;
  font-weight: 800;
  line-height: 1.25;
}

.romman-job-cta-text {
  margin-bottom: 1.6rem;
  color: rgba(253, 244, 245, 0.72);
  font-size: 0.92rem;
  line-height: 1.6;
}

@media (max-width: 575.98px) {
  .romman-jobs-track > .romman-job-tile {
    /* One at a time, held just short of full width so the next tile peeks in
       and the row reads as swipeable. */
    flex-basis: 86%;
  }
}

/* Sits under the pagination, so it needs its own spacing rather than the nav's
   — the nav is `hidden` whenever there is only one page, and margin on a hidden
   element collapses to nothing. */
.romman-jobs-footer {
  display: flex;
  justify-content: center;
  margin-top: 2rem;
}

/* --- Showcase backgrounds --------------------------------------------------
   One full-bleed layer per app, cross-fading as the section scrolls. The
   source images are a 160px-wide wash derived from each app's own first screen
   (see SyncAppScreenshots::background), so the "blur" is the browser's own
   upscaling rather than a per-frame filter pass.

   The layers reuse `data-romman-showcase-shot` — the hook the scroll module
   already drives — so replacing the poster cards with these needed no JS. */
.romman-showcase-bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}

.romman-showcase-bg-layer {
  position: absolute;
  inset: 0;
  /* HOW DARK THE ART IS — the only knob for it, and the reason it is a filter
     rather than a black layer in `background-image`: a multiply acts on the
     picture and nothing else. Veiling it through `background-image` means
     overwriting the element's own background to get at the art, and the veil
     then lands INSIDE this filter chain, where `brightness` below scales it
     back up again — a 0.4 black wash came out closer to 0.19 of one.

     Lower it to darken. 0.81 keeps the derived washes where they were: the
     chain used to end in brightness(1.35), and 1.35 x 0.6 is this. */
  --romman-showcase-art-dim: 0.81;
  background-position: center;
  /* `cover` here is for the DERIVED washes, and they cannot do without it: the
     source is 160px wide (0.46:1 for a phone screenshot), so at `auto` it would
     paint a 160px strip and leave the rest of the band transparent. Upscaling
     one is the whole design — the blur is the browser's own interpolation.

     The AUTHORED plates opt out of this in
     `.romman-showcase-bg-layer--art` below, because for them upscaling is the
     problem rather than the point. */
  background-size: cover;
  background-repeat: no-repeat;
  opacity: 0;
  transition: opacity 0.8s ease;
  /* A light blur on top of the upscale: it smooths the interpolation seams a
     160px source leaves behind on a very wide viewport. Cheap, because it is a
     static layer the compositor rasterises once. */
  /* Lifted: a 160px wash of a dark store poster reads as near-black at full
     bleed, and the point of these is the app's colour identity. */
  /* 48, not 24: several posters carry a hard-edged colour band (Al Salam's
     teal header), and at 24px that survived the upscale as a visible vertical
     seam across the section. This is enough to dissolve it into a field. */
  filter: blur(48px) saturate(1.6) brightness(var(--romman-showcase-art-dim));
  /* Grown past the frame so the blur's soft edge never shows as a border. */
  /* Grown further to match the wider blur — the soft edge a 48px blur leaves
     needs more overhang before it stops showing as a border. */
  scale: 1.25;
}

.romman-showcase-bg-layer.is-active {
  opacity: 1;
}

/* The copy column is first in the row, so it sits at the inline START — left in
   English, right in Arabic. The horizontal ramp has to follow it, or Arabic gets
   the clear end of the scrim under its text and the heavy end under the art. */
html[dir="rtl"] .romman-showcase-scrim {
  background:
    linear-gradient(
      to left,
      rgba(0, 0, 0, 0.94) 0%,
      rgba(0, 0, 0, 0.86) 32%,
      rgba(0, 0, 0, 0.55) 58%,
      rgba(0, 0, 0, 0.12) 84%,
      rgba(0, 0, 0, 0.04) 100%
    ),
    linear-gradient(180deg, #000000 0%, rgba(0, 0, 0, 0.1) 26%, rgba(0, 0, 0, 0.1) 74%, #000000 100%);
}

/* Above the background stack. */
.romman-showcase-sticky > .container {
  position: relative;
  z-index: 1;
}

@media (prefers-reduced-motion: reduce) {
  .romman-showcase-bg-layer {
    transition: none !important;
  }

  /* The stacked fallback shows every panel at once, so a single background
     would be arbitrary — the first one stays and the rest are dropped. */
  .romman-showcase-bg-layer:not(:first-child) {
    display: none;
  }

  .romman-showcase-bg-layer:first-child {
    opacity: 1;
  }
}

/* Scale never drops to 1: the art has to stay overscanned for the whole cycle
   or the drift would expose the frame edge at the extremes. */


@media (max-width: 991.98px) {

  /* Bootstrap's min-vh-100 is !important, and left on it would add a whole
     empty screen BELOW the art before the copy started. */
  .romman-riot-hero .row {
    min-height: 0 !important;
    padding-block: 0 !important;
  }

  /* pt-11 was clearing a fixed header the art now sits under, so on this
     layout it was just a ~140px hole between the picture and the kicker. */
  .romman-riot-hero .col-xl-7 {
    padding-top: 2rem !important;
  }

  /* The scrim exists to protect copy from artwork behind it. Nothing is behind
     the copy on a stacked layout — it would only muddy the art. */
  .romman-riot-hero-overlay {
    display: none;
  }
}

/* --- Hero: the brand globe -------------------------------------------------
   App icons on a rotating sphere around the pomegranate. Every node's position
   is written by romman-motion.js each frame as a transform — this file only
   sets what does not change. Nothing here positions the nodes, so editing
   these rules cannot fight the script. */
.romman-hero-globe {
  position: absolute;
  z-index: 0;
  /* Opposite the copy, and exactly its mirror: the copy column is half the
     container and this is the other half. */
  inset-block: 0;
  /* Two declarations, and they only work because this box lives INSIDE
     `.container` in the markup — so it is positioned against the same box the
     copy is laid out in, and 50% of it is genuinely half the hero.

     That is the whole fix for two separate bugs. The sphere used to be
     positioned against the HERO, which is the full viewport, while the copy is
     laid out in the container, which is centred and caps at 1200px. Anchored to
     the viewport edge it held its place beside the copy at exactly one width and
     drifted everywhere else — 870px clear of the container at 3840. Anchoring it
     to the container's edge with `calc(50% - 600px + …)` fixed the drift but
     hardcoded the container's cap, which then had to be maintained by hand and
     was wrong at the 960px step.

     Percentages of the containing block need neither. Logical properties, so
     Arabic mirrors it with the layout.

     Pulled in from BOTH edges of its half by the same amount, which keeps the
     sphere centred on that half while giving the outermost orbiting node room:
     that node reaches ~25px past this box at its 112px cap (`8.4vw`, measured),
     and at a 1200px viewport the container is flush to the screen so it has no
     side margin to lend — the node was being clipped by the hero's overflow. At
     28px the ink stops just short of the container's edge on one side and the
     centre line on the other, so it crosses neither. */
  inset-inline-end: 28px;
  width: calc(50% - 56px);
  display: grid;
  place-items: center;
  pointer-events: none;
}

/* The glow the sphere sits in. */
.romman-globe-halo {
  grid-area: 1 / 1;
  width: 76%;
  aspect-ratio: 1;
  border-radius: 50%;
  background: radial-gradient(circle,
    rgba(var(--brand-rgb), 0.5) 0%,
    rgba(var(--brand-rgb), 0.14) 42%,
    transparent 68%);
  filter: blur(38px);
  animation: romman-globe-pulse 9s ease-in-out infinite;
}

@keyframes romman-globe-pulse {
  0%, 100% { opacity: 0.75; transform: scale(1); }
  50%      { opacity: 1;    transform: scale(1.09); }
}

.romman-globe-orbit {
  grid-area: 1 / 1;
  position: relative;
  width: 100%;
  height: 100%;
}

/* Every node starts stacked dead centre; the script translates each one out to
   its projected position. `left/top: 50%` plus a -50% translate inside the
   script's transform would double up, so the centring is done here with
   margins and the script only ever writes translate/scale. */
.romman-globe-node {
  position: absolute;
  left: 50%;
  top: 50%;
  width: clamp(62px, 8.4vw, 112px);
  aspect-ratio: 1;
  margin-inline-start: calc(clamp(62px, 8.4vw, 112px) / -2);
  margin-block-start: calc(clamp(62px, 8.4vw, 112px) / -2);
  will-change: transform, opacity;
}

/* No rounded-square frame, no hairline, no plate. Those existed while the
   nodes were store icons — flat squares that needed an edge to sit on. These
   are cut-out illustrations that already carry their own glow and shadow, and
   boxing them would put a visible tile behind every transparent corner. */
.romman-globe-node img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  filter: drop-shadow(0 6px 16px rgba(0, 0, 0, 0.55));
}

.romman-globe-core {
  grid-area: 1 / 1;
  /* THE MIDPOINT of the node range, not above it — this is what makes the icons
     orbit the mark rather than circle politely behind it.

     romman-motion.js writes each node `Math.round(depth * 100)`, where depth is
     1 at the pole nearest the camera and 0 at the far one. The pomegranate sits
     at the sphere's centre, which is depth 0.5 — so 50 puts every near-side icon
     in front of it and every far-side icon behind it, and an icon crossing the
     mark passes over on its way toward you and under on its way back.

     This was 200 to stop a near-side icon covering the brand mark. That kept the
     mark clean and cost the sphere its depth: nothing ever went behind anything,
     so the orbit read as a flat ring with a logo pasted on top.

     Tied to the script's range, so the two have to move together. The nodes are
     absolutely positioned inside .romman-globe-orbit, which is `position:
     relative` with z-index auto and so does NOT open a stacking context — that
     is why their z-indices can interleave with this one at all. Giving the orbit
     a z-index, a transform or a filter would trap them below it and this would
     silently go back to a flat ring. */
  z-index: 50;
  width: 90%;
  display: grid;
  place-items: center;
  animation: romman-globe-bob 7s ease-in-out infinite;
}

.romman-globe-core img {
  width: 100%;
  height: auto;
  filter: drop-shadow(0 0 40px rgba(var(--brand-rgb), 0.6));
}

@keyframes romman-globe-bob {
  0%, 100% { transform: translateY(-1.5%); }
  50%      { transform: translateY(1.5%); }
}

@media (max-width: 991.98px) {
  /* In normal flow above the copy, same reasoning as the art it replaced: the
     hero paragraph runs long at 390px and an absolutely-placed globe beside it
     would be ridden into by the headline. */
  .romman-hero-globe {
    position: relative;
    inset: auto;
    /* Back out the container's gutter: this box sits inside `.container` now, so
       a plain 100% would inset the art by 12px a side and stop it bleeding to
       the screen edges the way it did as a child of the section. */
    width: calc(100% + var(--bs-gutter-x, 1.5rem));
    margin-inline: calc(var(--bs-gutter-x, 1.5rem) / -2);
    height: 42vh;
    margin-top: calc(var(--romman-header-h, 88px) + 0.5rem);
  }

  .romman-riot-hero .row {
    min-height: 0 !important;
    padding-block: 0 !important;
  }

  .romman-riot-hero .romman-hero-copy {
    padding-top: 1.5rem !important;
  }

  /* Nothing sits over artwork on a stacked layout. */
  .romman-riot-hero-overlay {
    display: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .romman-globe-halo,
  .romman-globe-core {
    animation: none !important;
  }
}

/* An AUTHORED showcase background — a real illustration dropped into
   public/images/art/backgrounds/ — rather than the 160px wash derived from an
   app's store art. The derived kind only works because it is blurred to 48px
   and overscanned; applying that here would throw away the picture. Enough
   blur to keep it behind the copy, and no upscale to fight. */
.romman-showcase-bg-layer--art {
  /* No blur. The shared layer blurs to 48px because the derived washes are
     160px sources that only work out of focus; authored art is meant to be
     seen, and the copy's legibility comes from the scrim over it instead.

     Darker than the derived washes because it carries real detail: an
     illustration behind a paragraph competes with it in a way a colour field
     does not. Measured on Kids Police, the strip the Arabic body copy sits in
     peaked at 226/255 from the lantern glow — that is the highlight this is
     holding down. 0.3 is the 50% it used to be, times the 0.4 black wash that
     was briefly layered into `background-image`. */
  --romman-showcase-art-dim: 0.3;
  filter: brightness(var(--romman-showcase-art-dim));
  /* NO SCALING on a large screen. These plates are ~1536-1672px wide, so any
     sizing keyword upscales them once the band is both wide and tall: at
     2560x1440 `contain` lands on a 1.53x blow-up and `cover` on more, and a
     flat illustration shows that as visible softness and stepped edges.
     Painting at intrinsic size keeps it sharp.

     What it costs: on a viewport wider than the plate, the art no longer
     reaches the edges. That is survivable here and nowhere near as visible as
     the pixelation — the plates are built on a dark ground that reads as a
     deliberate centred vignette against the section's near-black, and the
     scrim sits over the join.

     Phones and tablets get `contain` instead, in the block below: there the art
     is a defined 3:2 band rather than a full-bleed layer, and it is a downscale
     from 1536px, so it is both sharp and complete. */
  background-size: auto;
  scale: 1;
}

/* ==========================================================================
   SHOWCASE ART ON TOP (phones and small tablets)

   The art stops being a background and becomes a band of its own above the copy,
   the way the hero already stacks on a phone.

   As a full-bleed BACKGROUND it was in an unwinnable spot: the copy sat over it,
   so it had to be dimmed to 30% for an Arabic paragraph to stay readable — which
   left the illustration barely visible, and the paragraph was still on a busy
   plate anyway, because the scrim meant to darken the copy side is commented
   out. In flow it can actually be seen and the copy gets clean ground. It still
   cross-fades as you swipe: the layers stay absolute inside this box.

   DECLARED HERE, not up in the main phone block, and that is not tidiness: the
   rules it overrides — `.romman-showcase-bg`'s `position: absolute` and
   `.romman-showcase-bg-layer--art`'s own `--romman-showcase-art-dim` — are both
   further down the file than that block, and a media query adds no specificity.
   Declared up there these were silently discarded.
   ========================================================================== */
@media (max-width: 991.98px) {
  .romman-showcase-bg {
    position: relative;
    /* Explicit, and load-bearing: with `aspect-ratio` and a `max-height` but no
       definite width, the box sizes its WIDTH from the clamped height instead —
       which left the art 322px wide in an 844px landscape viewport rather than
       full-bleed. */
    width: 100%;
    aspect-ratio: 3 / 2;
    /* Guards the landscape case, where 3:2 of an 844px width would be 563px of
       art in a 390px-tall viewport. The layer's own background-size absorbs the
       ratio being overridden — see the aspect-ratio guard further down. */
    max-height: 55vh;
    margin-bottom: 2.25rem;
  }

  /* THE WHOLE PICTURE, on phones and tablets.
     Here the art is a band of its own at a defined 3:2 rather than a full-bleed
     layer, and the plates are ~1536px wide, so `contain` is a DOWNSCALE into a
     390-820px box: sharp, and nothing cropped off. That is the opposite of what
     the same keyword does on a large screen, where it upscales — which is why
     this is scoped here and `.romman-showcase-bg-layer--art` stays at `auto`.

     Only the authored plates. The derived washes keep the shared `cover`: a
     0.46:1 strip contained in a 3:2 band would sit as a sliver with the rest of
     the band empty. */
  .romman-showcase-bg-layer--art {
    background-size: contain;
  }

  /* Nothing is laid over it any more, so it does not need holding down. Both
     selectors, because the authored-art rule sets this variable too. */
  .romman-showcase-bg-layer,
  .romman-showcase-bg-layer--art {
    --romman-showcase-art-dim: 0.78;
  }

  /* Matched to the panels' own 0.3s, so the picture and the words change
     together. The shared 0.8s is right on desktop, where the cross-fade is
     driven by a slow scroll through a three-viewport section; on a rail the slide
     is over in a few hundred milliseconds and 0.8s read as the art lagging
     behind. */
  .romman-showcase-bg-layer {
    transition: opacity 0.35s ease;
  }
}

/* Landscape phones, where the band is no longer 3:2.
   `max-height: 55vh` above overrides the aspect-ratio, and once the box is
   proportionally wider than the plate, `contain` stops filling it and leaves
   empty ground down both sides — an 844x390 viewport gives a 844x215 band, and
   a 1.5:1 plate contained in it covers barely a third of the width. Filling is
   the better failure there, so this hands those viewports back to `cover`.

   33/40 is 0.825, which is where the clamp starts to bite: the band loses its
   ratio once 0.55 x height < width / 1.5. Both of those numbers live in the
   block above — change either and this fraction has to move with it. */
@media (max-width: 991.98px) and (min-aspect-ratio: 33/40) {
  .romman-showcase-bg-layer--art {
    background-size: cover;
  }
}


/* Store icons need the frame back that the illustrated cut-outs did not: they
   are flat squares, several of them artwork on a near-black ground, and
   without an edge they dissolve into the hero. Scoped to the 'store' source so
   flipping $globeSource carries its own styling with it. */
.romman-hero-globe--store .romman-globe-node img {
  border-radius: 24%;
  object-fit: cover;
  background: rgba(255, 255, 255, 0.05);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.12), 0 10px 22px rgba(0, 0, 0, 0.55);
  filter: none;
}


/* --- Rail: the browse-all CTA it inherited -------------------------------- */
.romman-rail-cta {
  display: flex;
  justify-content: center;
  margin-top: 3rem;
}


/* --- How we work — three alternating rows -----------------------------------
   Copy and artwork trade sides down the section. Each row is its own
   [data-romman-band], so the three points arrive and leave on their own edges
   rather than the section moving as one slab.

   Two earlier shapes are worth recording, because both were reverted for
   reasons that still apply:

     pinned reel   three viewports of scroll to deliver 28 words, with 45% of
                   every frame empty and only a 272px stage ever changing.
     three-up grid fixed the waste — one screen, no dead space — but there was
                   nowhere to put an illustration, and the section wanted one.

   Rows are the version that is both full-height and full of something. The
   height comes from the artwork, not from padding.

   The old "typographic on purpose, no key art" rule is deliberately retired
   here: what it was guarding against was a fourth run of PORTFOLIO imagery.
   A purpose-drawn illustration per point is not that. */
.romman-values-band {
  position: relative;
  z-index: 2;
  padding-block: 7rem 5rem;
  background: transparent;
}

.romman-values-head {
  max-width: 46rem;
  margin-inline: auto;
  margin-bottom: 3.5rem;
  text-align: center;
}

.romman-values-band .romman-section-title {
  color: #ffffff;
}

.romman-values-band .romman-section-copy,
html[dir="rtl"] .romman-values-band .romman-section-copy {
  max-width: 44rem;
  color: rgba(253, 244, 245, 0.7);
}

/* Compact rather than a screen each: three full-height rows is the reference's
   proportion, but it puts the third point two and a half screens below the
   heading that frames it. 55vh keeps every row taller than its own content
   while landing the whole section near 1.8 screens. */
/* 46vh is a FLOOR, not the height: on a normal desktop the capped artwork is
   taller than it and sets the row. It matters on short and very tall viewports,
   where it keeps a row from collapsing onto its neighbour. */
.romman-value-row {
  display: flex;
  align-items: center;
  min-height: 26vh;
  padding-block: 2rem;
}

.romman-value-row-inner {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: center;
  gap: 3rem;
  width: 100%;
}

/* Alternation by explicit grid placement, not `order`: placement moves the
   boxes but leaves the DOM sequence alone, so the reading order a screen
   reader and the keyboard follow is still copy-then-art on every row.

   Nothing here is direction-specific. Under `dir="rtl"` the grid mirrors
   itself, so the zig-zag survives translation without a second rule. */
.romman-value-row--flip .romman-value-copy {
  grid-column: 2;
  grid-row: 1;
}

.romman-value-row--flip .romman-value-art {
  grid-column: 1;
  grid-row: 1;
}

.romman-value-copy {
  max-width: 30rem;
}

.romman-value-mark {
  display: flex;
  align-items: center;
  gap: 0.8rem;
  margin-bottom: 1.1rem;
  color: var(--brand);
}

/* The glyph already carries --brand-gradient-text, clipped to its strokes by the
   shared rule further up. This lights it.

   `filter: drop-shadow`, not `text-shadow`. The fill here is a background
   clipped to the glyph and `-webkit-text-fill-color: transparent` on top of it,
   and a text-shadow under that arrangement paints inconsistently between
   engines — sometimes beneath the clipped background, sometimes not at all.
   drop-shadow works on the element's rendered alpha, so it sees the clipped
   glyph exactly as drawn and cannot be covered by it.

   Three stacked shadows rather than one, following the same ramp the glyph is
   filled with: a tight brand-red core, plum at mid distance, a wide periwinkle
   halo. One flat shadow in a single colour reads as a blur behind the icon;
   walking the ramp outward reads as the icon emitting its own gradient. Widths
   roughly double each step so the falloff stays smooth. */
.romman-value-mark i {
  font-size: 2rem;
  line-height: 1;
  filter:
    drop-shadow(0 0 5px rgba(var(--brand-rgb), 0.55))
    drop-shadow(0 0 12px rgba(var(--brand-plum-rgb), 0.40))
    drop-shadow(0 0 24px rgba(var(--font-gradient-fifth-rgb), 0.22));
}

.romman-value-mark em {
  font-size: 0.9rem;
  font-weight: 800;
  font-style: normal;
  letter-spacing: 0.2em;
  color: rgba(253, 244, 245, 0.35);
  font-variant-numeric: tabular-nums;
}

.romman-value-title {
  margin-bottom: 1rem;
  color: #ffffff;
  font-size: clamp(1.6rem, 2.6vw, 2.4rem);
  font-weight: 800;
  line-height: 1.2;
  letter-spacing: -0.02em;
}

.romman-value-text {
  margin: 0;
  color: rgba(253, 244, 245, 0.7);
  font-size: 1.05rem;
  line-height: 1.7;
}

/* The art side holds its 4:3 box whether or not the picture exists yet, so
   dropping the files in later cannot reflow the section or shift the rows. */
/* Capped, and the cap is what sets the row height.

   Left uncapped the art filled its whole 1fr column — ~576px wide at this
   container, so 432px tall at 4:3 — and each row grew to match. Three of those
   plus the heading put the section at 2.1 screens, over the brief. The width
   cap is the honest lever: `min-height` cannot shrink a row whose content is
   already taller than it. */
.romman-value-art {
  position: relative;
  aspect-ratio: 4 / 3;
  display: grid;
  place-items: center;
  width: 100%;
  max-width: 30rem;
  margin-inline: auto;
}

/* The gradient bloom the illustration sits in.

   Three offset radial stops rather than one centred blob, walking the brand
   ramp across the box — crimson high and central, plum low and leading, a cool
   periwinkle wash trailing. A single symmetrical glow reads as a vignette
   behind a picture; offsetting the stops makes the light look like it comes
   FROM the object, which is what the artwork itself is drawing.

   Wider than the box (`inset: -12%`) and elliptical, so it fades out into the
   wind rather than stopping at an invisible square edge.

   Painted here rather than baked into the WebP for two reasons: it stays
   editable with the palette tokens, and a soft full-bleed gradient is the most
   expensive thing to encode — it was a measurable part of why the first alpha
   exports came back at 240KB. */
.romman-value-art--filled::before {
  content: '';
  position: absolute;
  inset: -14%;
  /* Every stop fades to its OWN hue at zero alpha, never to `transparent`.
     `transparent` is rgba(0,0,0,0), so a stop running to it interpolates its
     colour toward BLACK on the way out — the fade greys off, and three of them
     overlapping compound it. Measured on the first attempt: the halo came out
     bluer than the page it sat on (R-B of -14 against the surrounding -8),
     which is the exact opposite of a brand glow, and read on screen as a pale
     grey disc behind the artwork.

     `ellipse closest-side`, and every stop reaches zero alpha at 100%. That
     combination is what removes the visible rim: closest-side puts the fade's
     end at the NEAREST box edge, so alpha is already zero everywhere on the
     boundary and in the corners beyond it. The first attempt used the default
     farthest-corner, which was still painting colour when it hit the element's
     edge — so the box clipped it and the bloom read as a grey spotlight oval
     with a hard rim rather than as light. Nothing is clipped now, which is also
     why there is no border-radius here any more; there is nothing to round. */
  background:
    radial-gradient(ellipse closest-side at 52% 44%, rgba(var(--brand-rgb), 0.34), rgba(var(--brand-rgb), 0) 100%),
    radial-gradient(ellipse closest-side at 40% 62%, rgba(var(--brand-plum-rgb), 0.24), rgba(var(--brand-plum-rgb), 0) 100%),
    radial-gradient(ellipse closest-side at 66% 58%, rgba(var(--font-gradient-fifth-rgb), 0.10), rgba(var(--font-gradient-fifth-rgb), 0) 100%);
  pointer-events: none;
}

/* `position: relative` is load-bearing: ::before is absolutely positioned, and
   a positioned element paints above a static one no matter the source order —
   without this the bloom washes over the artwork instead of sitting behind it. */
.romman-value-art img {
  position: relative;
  width: 100%;
  height: 100%;
  object-fit: contain;
  /* The art is emissive on transparent now, so drop-shadow traces the object's
     own silhouette rather than a rectangle. Two stops, tight and warm: this is
     the object's contact glow, where ::before above is the ambient one it sits
     in. Kept cheap — the row's motion target animates transform and opacity, so
     anything expensive here is re-composited while the section moves. */
   filter: drop-shadow(0 0 50px rgba(var(--brand-rgb), 0.30)) drop-shadow(0 0 68px rgba(var(--brand-plum-rgb), 0.92));
}

/* PLACEHOLDER — deliberately finished-looking rather than an empty box, so the
   layout can be judged before the art lands: the point's own mark, oversized,
   in the soft radial bloom the illustrations will sit in. Delete nothing to
   remove it; it disappears on its own the moment the file exists. */
.romman-value-art-stub {
  position: relative;
  display: grid;
  place-items: center;
  width: 78%;
  aspect-ratio: 1;
  border-radius: 50%;
  /* Strong enough to READ as a deliberate placeholder. The first pass sat at
     0.28/0.22 alpha, which over this page's near-black canvas left the icon
     apparently floating in nothing — impossible to judge the composition
     against, which is the whole job of a placeholder. */
  background:
    radial-gradient(circle at 50% 42%, rgba(209, 54, 58, 0.55), transparent 58%),
    radial-gradient(circle at 50% 58%, rgba(104, 68, 190, 0.45), transparent 66%),
    radial-gradient(circle at 50% 50%, rgba(255, 255, 255, 0.05), transparent 72%);
}

/* A hairline ring, so the box the illustration will occupy is legible even
   where the bloom fades into the page. */
.romman-value-art-stub::after {
  content: '';
  position: absolute;
  inset: 8%;
  border: 1px solid rgba(253, 244, 245, 0.09);
  border-radius: 50%;
}

.romman-value-art-stub i {
  position: relative;
  font-size: clamp(3.4rem, 7vw, 5.5rem);
  color: rgba(253, 244, 245, 0.82);
}

@media (max-width: 991.98px) {
  .romman-values-band {
    padding-block: 4.5rem 3.5rem;
  }

  .romman-values-head {
    margin-bottom: 2.5rem;
  }

  /* One column, and the art goes ABOVE its copy on every row — including the
     flipped ones, whose explicit placement is undone here. Alternating sides
     has no meaning in a single column, and leaving the flip in place would put
     two illustrations back to back across a row boundary. */
  .romman-value-row {
    min-height: 0;
    padding-block: 1.5rem;
  }

  .romman-value-row-inner {
    grid-template-columns: minmax(0, 1fr);
    gap: 1.5rem;
  }

  .romman-value-row--flip .romman-value-copy,
  .romman-value-row--flip .romman-value-art {
    grid-column: 1;
    grid-row: auto;
  }

  /* Centred, unlike the desktop rows.

     On desktop the copy is a column beside the artwork and its left edge is the
     row's structure — the zig-zag is only legible because each block starts on
     a shared axis. Stacked, that axis is gone: the illustration centres itself
     in the column and left-aligned copy underneath reads as a block that has
     slipped off its own picture. The mark row needs the same treatment or the
     icon and its numeral sit hard left under a centred image. */
  .romman-value-copy {
    max-width: none;
    order: 2;
    text-align: center;
  }

  .romman-value-mark {
    justify-content: center;
  }

  /* Held short and centred rather than running the full column: a paragraph
     that spans the whole phone width is a worse measure AND makes the ragged
     edge look like a mistake once the text above it is centred. */
  .romman-value-text {
    max-width: 34rem;
    margin-inline: auto;
  }

  /* No aspect-ratio override here. There was a `16 / 10` on this rule meant to
     shorten the stacked rows, and it silently did nothing: the computed value
     was 16/10 but the box measured 366x275, which is 4:3.

     Why it loses — the box is a grid container whose img is `height: 100%`. A
     percentage height resolved against a height that aspect-ratio is itself
     deriving is cyclic, so the image falls back to its intrinsic 4:3, and the
     grid's automatic minimum size (`min-height: auto`) then lets that content
     push the box past the ratio. The declaration was outranked by its own
     child.

     Keeping 4:3 rather than forcing the ratio through with `min-height: 0`,
     because 16/10 was the wrong goal anyway: the art IS 4:3, so a wider box
     only letterboxes it and wastes the phone's narrow width. */
  .romman-value-art {
    order: 1;
  }

  .romman-value-title {
    font-size: clamp(1.5rem, 6vw, 2rem);
  }

  .romman-value-text {
    font-size: 1rem;
  }
}

/* --- Brand parallax --------------------------------------------------------
   One oversized pomegranate behind everything from the top of the page to the
   end of "How we work", moving slower than the scroll. Fixed rather than
   absolute: the whole point is that it does NOT travel with the content, so
   the hero, the metrics and the three points feel like one continuous space.

   romman-motion.js writes the transform and opacity; nothing here positions it
   on the scroll axis, so editing these rules cannot fight the script. */
.romman-brand-parallax {
  position: fixed;
  inset: 0;
  z-index: 0;
  display: grid;
  place-items: center;
  overflow: hidden;
  pointer-events: none;
  /* Its own layer — this is a large blurred bitmap that would otherwise be
     re-rasterised against the page on every scroll frame. */
  will-change: transform, opacity;
  contain: strict;
}

.romman-brand-parallax img {
  width: min(92vh, 900px);
  height: auto;
  /* Faint and soft: it is a watermark behind live copy, not a picture. The
     script scales this from the same element, so no transform belongs here. */
  opacity: 0.9;
  filter: blur(2px) saturate(0.9);
}

@media (max-width: 991.98px) {
  /* Smaller and fainter: on a phone the copy sits over the middle of the
     screen, which is exactly where this lands. */
  .romman-brand-parallax img {
    width: min(70vh, 460px);
  }
}

/* --- Portfolio to Open Positions hand-over ---------------------------------
   The band carries extra height and its inner block sticks, so the last stretch
   of scrolling zooms the portfolio toward the viewer and fades it out rather
   than simply scrolling it away. romman-motion.js writes the transform.

   The band already had overflow:hidden for the marquee, which is also what
   keeps the scaled-up content from spilling onto the section below. */
.romman-rail-band[data-romman-zoom] {
  min-height: calc((1 + var(--romman-zoom-run, 0.85)) * 100vh);
  /* The rows are children of the sticky block now, so the band's own vertical
     padding would double up with the block's. */
  padding-block: 0;
}

.romman-rail-zoom {
  position: sticky;
  top: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 100vh;
  padding-block: calc(var(--romman-header-h, 88px) + 2rem) 4rem;
  /* Grows from the centre, so it reads as coming toward the viewer rather than
     sliding off one edge. */
  transform-origin: 50% 50%;
  will-change: transform, opacity;
}

@media (max-width: 991.98px) {
  /* No zoom on a phone: it would spend most of a screen of scrolling on an
     effect that largely plays off-frame at this width. */
  .romman-rail-band[data-romman-zoom] {
    min-height: 0;
    padding-block: 4rem;
  }

  .romman-rail-zoom {
    position: static;
    min-height: 0;
    padding-block: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .romman-rail-band[data-romman-zoom] {
    min-height: 0;
    padding-block: 6rem;
  }

  .romman-rail-zoom {
    position: static;
    min-height: 0;
    padding-block: 0;
  }
}

/* --- Wind: the sitewide wisp layer -----------------------------------------
   Replaces the still wisp plate that used to sit in the hero only. That was a
   render which had to be blurred to hide its own cellular texture, could not
   move, and lived in one section; this is a drawn canvas fixed behind the whole
   page, so every section scrolls through the same moving air.

   romman-motion.js draws it at a THIRD of viewport resolution and CSS scales it
   up — the blur throws that detail away regardless, and it is ~9x less fill per
   frame. The blur here is doing real work, not hiding a flaw: it turns five
   stroked lines into soft air. */
.romman-wind {
  position: fixed;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  /* Wide enough that the strokes stop reading as lines and become air. Coupled
     to SCALE in romman-motion.js: this blur is what lets the canvas render at a
     third of viewport resolution without showing the upscale. */
  filter: blur(46px);
  opacity: 0.62;
  /* Its own compositor layer: without this the blurred canvas is re-rasterised
     against the page on every scroll frame, which is the whole cost of the
     effect paid twice. */
  will-change: transform;
  transform: translateZ(0);
  contain: strict;
}

@media (max-width: 991.98px) {
  /* Softer on a phone: the ribbons are sized in viewport fractions, so at this
     width they crowd the copy far more than they do on a desktop. */
  .romman-wind {
    filter: blur(34px) saturate(1.2);
    opacity: 0.45;
  }
}

/* --- Open Positions: the arrival ------------------------------------------
   Mirrors the portfolio hand-over above. That section leaves by growing toward
   the viewer and fading out; this one arrives by receding back to its natural
   size. romman-motion.js writes the transform on entry.

   transform-origin at the top: the section is taller than the viewport when it
   starts entering, so growing from the centre would push its heading up out of
   frame before it ever settled. */
.romman-jobs-enter {
  transform-origin: 50% 0%;
  /* `transform` only. `will-change: opacity` creates a backdrop root just as a
     real opacity does, and the job cards inside use backdrop-filter — with it
     listed here their blur sampled an isolated empty group and the section
     rendered as a pale slab with a visible rectangle around it. */
  will-change: transform;
}

/* Pulled up over the portfolio's tail.

   The portfolio's sticky block is a full viewport tall, so once its content has
   faded to nothing there was still ~900px of empty section scrolling past before
   this one began — a dead screen between the two. Overlapping means Open
   Positions starts arriving while that faded block is still on its way out, so
   the hand-over is continuous instead of separated by a gap.

   z-index above the portfolio: the two boxes now overlap, and without it this
   section would be painted underneath the one it is replacing. */
.romman-jobs-band[data-romman-enter] {
  margin-top: -42vh;
  z-index: 4;
}

@media (max-width: 991.98px) {
  /* No zoom on a phone, so there is no tail to close — the overlap would just
     pull the section over live content. */
  .romman-jobs-band[data-romman-enter] {
    margin-top: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .romman-jobs-band[data-romman-enter] {
    margin-top: 0;
  }
}

@media (max-width: 991.98px) {
  .romman-jobs-enter {
    transform: none !important;
  }
}

@media (prefers-reduced-motion: reduce) {
  .romman-jobs-enter {
    transform: none !important;
  }
}

/* --- Short-viewport fallback for the scroll-stepped sections ---------------
   Both step modules release on a viewport too short to hold a sticky stage
   (`max-height: 460px` in romman-motion.js — a landscape phone). When they
   release they strip `.is-active` from every step, so WITHOUT this the sections
   render completely blank: the steps sit at opacity 0 with nothing to turn them
   back on.

   This must stay matched to the JS query. It used to be a width breakpoint in
   both places; when the modules moved to a height gate and this block was still
   keyed to width, the landscape case fell through the crack and showed nothing. */
@media (max-height: 460px) {
  /* "How we work" is plain rows now — it has no sticky stage to unstick, so
     only the showcase is left to handle here. */
  .romman-showcase {
    min-height: 0;
  }

  .romman-showcase-sticky {
    position: static;
    min-height: 0;
    padding-block: 3rem;
  }

  .romman-showcase-panels {
    display: block;
  }

  .romman-showcase-panel {
    opacity: 1;
    transform: none;
    pointer-events: auto;
  }

  .romman-showcase-panel + .romman-showcase-panel {
    margin-top: 2.5rem;
  }

  /* The module also clears `.is-active` from the background layers, which would
     leave the showcase with no background at all. One layer, always on. */
  .romman-showcase-bg-layer:not(:first-child) {
    display: none;
  }

  .romman-showcase-bg-layer:first-child {
    opacity: 1;
  }

  .romman-showcase-dots {
    display: none;
  }
}
