/* Use common system fonts instead of Roboto for a more natural look */

/* OVH: the frame must never scroll. The document is exactly one viewport tall, laid out
   as a column whose flexible child is the gauge app and whose fixed footer is the
   credits, so there is nothing to overflow. Upstream left the credits *after* a 100vh
   app, which made the page 100vh plus the credits tall; because the stylesheet also
   hides scrollbars, that overflow was invisible, but a wheel over the gauge or an
   elastic scroll on macOS shifted the whole UI and revealed blank space above the
   dial. */
html {
  height: 100%;
  overflow: hidden;
}

body {
  margin: 0px;
  padding: 0px;
  display: flex;
  flex-direction: column;
  height: 100vh;
  overflow: hidden;
}

::-webkit-scrollbar {
  display: none;
}
html {
  -ms-overflow-style: none; 
  scrollbar-width: none;
}
 
.Credits {
/* OVH: was rgb(125 119 119) -> 4.40:1 on white, under the 4.5:1 AA floor. */
color: #787272;
text-align: center;
/* OVH: 14px -> 11px, so the attribution line reads a touch smaller without
   losing legibility. Re-apply on OpenSpeedTest upgrade. */
font-size:11px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
font-weight: 500;
/* OVH: flex child so it keeps its own height and the app fills the rest. */
flex: 0 0 auto;
padding: 4px 12px 6px;
} 
.Credits a {
  text-decoration: none;
  color: rgb(113, 113, 113);
}
.Credits a:hover {
color: #0050d7;
}

.ConnectError {
  display: none;

}

.openSpeedtestApp {
 
  /* OVH: this was 100vh, the full iframe, with the .Credits block laid out *below* it, so
     the document was always about 35px taller than the frame and scrolled internally.
     Worse, this stylesheet hides scrollbars, so the overflow was invisible. Now the flex
     column above reserves the credits' own height and this element takes the rest
     (height: auto plus flex-grow), so the SVG fills the available box exactly and the
     whole frame stays 100vh. */
  flex: 1 1 auto;
  min-height: 0;
  height: auto;
  width: 100%;
  display: none;
  overflow: hidden;
}
.main-Gaugebg {
  fill: none;
  stroke: rgb(231, 231, 232);
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-width: 22px;
  stroke-dasharray: 681;
}
.main-GaugeBlue {
  fill: none;
  stroke: url(#gradient);
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-width: 22px;
  stroke-dasharray: 681;
  stroke-opacity: 0;
}
.main-GaugeWhite {
  fill: none;
  stroke: rgb(255, 255, 255);
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-width: 15px;
  stroke-dasharray: 0, 681;
  stroke-dashoffset: 1;
  stroke-opacity: 0;
}
.oDo-Meter {
  font-size: 16.633283615112305px;
  fill: gray;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  font-weight: 500;
}
.oDoLive-Speed {
  font-size: 28px;
  fill: #201e1e;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  font-weight: 500;
  text-anchor: middle;
}

.oDoLive-Status {
  font-size: 10px;
  fill: #d2d1d2;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  font-weight: 500;
  text-anchor: middle;
}
.uiBg {
  fill: #d2d1d2;
}
.progressbg {
  stroke: rgb(231, 231, 232);
  stroke-width: 8px;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 400;
  stroke-dashoffset: 0;
}
.Cards {
  fill: var(--rcCard, #f2f2f2);
}
.Symbol {
  fill: var(--rcArrow, url(#gradient));
}
.rtext {
  font-size: 12px;
  fill: var(--rcCaption, #333);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  font-weight: 500;
}
/* OVH: the value and the unit live in the SAME symbol (downResultC1 and friends hold
   both, and jitterMob holds the whole PING block), so the only thing that can size them
   apart is the <use> they are cloned through: both inherit its font-size, and these two
   rules then split it. The value takes the whole inherited size (1em) and the unit a
   fraction of it, which keeps upstream's 23:12 ratio at whatever size the layout asks
   for.
   That indirection is what the mobile layout needs. Its cards are 88.1 units wide
   against the desktop card's 278.1, but both drew the value at 23px, so a six-digit
   reading measured 85.9 units, 97.5% of the mobile card and touching both edges
   ("some text size for value or unit are a bit too big", 2026-09-11). The per-layout
   sizes are set on the uses just below. Re-apply on upgrade. */
.rtextnum {
  font-size: 1em;
  fill: var(--rcValue, #201e1e);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  font-weight: 500;
  text-anchor: middle;
}
.rtextmbms {
  font-size: 0.522em;   /* 12/23 of the value, as upstream drew it */
  fill: var(--rcUnit, #5f5f5f);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  font-weight: 500;
  text-anchor: middle;
}
.jitter-Mob {
  font-size: 0.391em;   /* 9/23 */
  fill: #5f5f5f;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  font-weight: 500;
  text-anchor: middle;
}

/* The reading size per layout. Desktop is upstream's 23px; the mobile layout drops to
   17px, which puts a six-digit value at about 72% of its card (63 units of 88.1)
   instead of 97.5%, with the unit following at 0.522em = 8.9px. Attribute selectors are
   used because the result <use>s carry no class of their own, and `[*|href]` matches
   xlink:href in every namespace. */
#UI-Desk use[*|href="#downResultC1"],
#UI-Desk use[*|href="#upResultC2"],
#UI-Desk use[*|href="#pingResultC3"],
#UI-Desk use[*|href="#jitterResultC3"],
#UI-Mob use[*|href="#downResultC1"],
#UI-Mob use[*|href="#upResultC2"],
#UI-Mob use[*|href="#jitterMob"] {
  font-size: 17px;
}
#UI-Desk use[*|href="#downResultC1"],
#UI-Desk use[*|href="#upResultC2"],
#UI-Desk use[*|href="#pingResultC3"],
#UI-Desk use[*|href="#jitterResultC3"] {
  font-size: 23px;
}
.startButton {
  fill: url(#RadialGradient1);
  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
  -webkit-tap-highlight-color: transparent;
  cursor: pointer;
  pointer-events: visible;
}
.buttonTxt {
  font-size: 40px;
  fill: #ffffff;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  font-weight: 500;
  text-anchor: middle;
}

.intro-Progress {
  stroke: #0050d7;
  stroke-width: 8px;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 400;
  stroke-dashoffset: 0;
}
.progressElmstart {
  stroke: #0050d7;
  stroke-width: 8px;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 400;
  stroke-dashoffset: 0;
  display: block;
}
.Startsettings {
  fill: url(#RadialGradient1);
  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
  -webkit-tap-highlight-color: transparent;
  cursor: pointer;
  pointer-events: visible;
  opacity: 0.1;
  transition: opacity 1s ease-in-out;
}
.Startsettings:hover {
  opacity: 1;
}

.progressbg {
  stroke: rgb(231, 231, 232);
  stroke-width: 8px;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 400;
  stroke-dashoffset: 0;
}
.deskStart {
  fill: none;
  stroke: rgb(231, 231, 232);
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-width: 22px;
  stroke-dasharray: 681;
  stroke: url(#gradient);
}
#UI-Desk {
  display: none;
}
#UI-Mob {
  display: none;
}
.oDoTop-Speed {
  font-size: 16.96px;
  fill: gray;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  font-weight: 500;
  text-anchor: end;
}
#upSymbolDesk {
  fill: #000e9c;
  display: none;
}
#downSymbolDesk {
  fill: #000e9c;
  display: none;
}
#upSymbolMob {
  fill: #000e9c;
  display: none;
}
#downSymbolMob {
  fill: #000e9c;
  display: none;
}

#ipMob {
  font-size: 15px;
  fill: #201e1e;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  font-weight: 500;
  text-anchor: middle;
  display: none;
}
#ipDesk {
  font-size: 15px;
  fill: #201e1e;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  font-weight: 500;
  text-anchor: middle;
  display: none;
}

.spinner {
  position: absolute;
  z-index: 999;
  top: 50vh;
  left: 50vw;
  text-align: center;
}

.spinner > div {
  width: 20px;
  height: 20px;
  background-color: #000e9c;

  border-radius: 100%;
  display: inline-block;
  -webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both;
  animation: sk-bouncedelay 1.4s infinite ease-in-out both;
}

.spinner .bounce1 {
  -webkit-animation-delay: -0.32s;
  animation-delay: -0.32s;
}

.spinner .bounce2 {
  -webkit-animation-delay: -0.16s;
  animation-delay: -0.16s;
}

@-webkit-keyframes sk-bouncedelay {
  0%,
  80%,
  100% {
    -webkit-transform: scale(0);
  }
  40% {
    -webkit-transform: scale(1);
  }
}

@keyframes sk-bouncedelay {
  0%,
  80%,
  100% {
    -webkit-transform: scale(0);
    transform: scale(0);
  }
  40% {
    -webkit-transform: scale(1);
    transform: scale(1);
  }
}

.darkmode {
  margin-bottom: -15px;
  fill: #75757a99;
  padding-top: 16px;
  display: none;
  cursor: pointer;
  margin-right: 30px;
}
#daymode {
  margin-right: 40px;
}
.darkmode:hover {
  fill: #000000;
}

/* The nested SVG viewports fill their parent (the flexed .openSpeedtestApp) rather than
   the viewport. With 100vh they were sized to the *frame*, one viewport taller than the
   box the credits had left, and the SVG then scaled to that bigger box: the dial was
   pushed down and the extra height became the top blank the wheel could reveal. A height
   of 100% ties the scale to the box it actually occupies. */
.Mobile,
.Desktop {
  visibility: hidden;
  width: 100%;
  height: 100%;
}

@media only screen and (orientation: landscape) {
  .Mobile {
    visibility: hidden;
  }
  .Desktop {
    visibility: visible;
  }
}

@media only screen and (orientation: portrait) {
  .spinner {
    top: 42vh;
    left: 42vw;
  }
  .Mobile {
    visibility: visible;
  }
  .Desktop {
    visibility: hidden;
  }
}
/* OVH: upstream drops the attribution outright below a 300px-wide frame. Our own page can
   hand the frame less than 300px: its width is derived from its height (3/4) but capped
   by the panel (max-width:100%), so on a phone narrower than about 352 CSS px the
   410px-tall frame renders 272-292px wide and this rule silently deleted the licence
   line, measured 2026-09-20 on a Pixel with Firefox after the same line had already been
   found below the fold. The attribution is a licence condition, so it does not get to
   disappear: a narrow frame makes it smaller and tighter instead. No layout rule outside
   this file can guarantee 301px, because at a 320px viewport even a full-width frame is
   296px. Re-apply on OpenSpeedTest upgrade. */
@media only screen and (max-width: 300px) {
  .Credits{
    font-size:10px;
    line-height:1.3;
    padding:4px 6px 5px;
  }
}

/* OVH: the text that replaces the logo, and the gauge gradients, carry the brand colours
   in the light skin. darkmode.css lifts them for the dark ground, the same split as
   .Credits. */
.ostStartHint {
  fill: #000e9c;
}

/* OVH: the ping icon (ODS 'refresh') used to carry an inline fill, so the light and dark
   skins could not differ. Brand value here, and darkmode.css lifts it. */
.pingIc {
  fill: var(--rcIcon, #000e9c);
}

/* OVH: the OVHcloud wordmark shown under the "Ready" hint. Brand navy on the light
   ground, the lifted blue in darkmode.css. */
.ovhLogo {
  fill: #000e9c;
}

/* ============================================================================
   OVH: motion. Upstream only cross-faded whole groups, so a run felt static: the gauge
   moved, but the numbers and cards appeared with no life of their own. These are
   presentation-only, touching no measurement code, and all of them are dropped under
   prefers-reduced-motion.
   ========================================================================== */

/* The three result cards rise in one after another when the results are shown.
   #UI-Desk and #UI-Mob go from display:none to block, which restarts CSS animations, so
   every run gets the reveal. The x and y are the upstream coordinates in app.svg, used
   to stagger desktop by y and mobile by x. */
@keyframes cardIn {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: translateY(0); }
}
use.Cards { animation: cardIn 520ms cubic-bezier(.22,.61,.36,1) both; }
use.Cards[y="31"],    use.Cards[x="1"]     { animation-delay: 0ms; }
use.Cards[y="129.3"], use.Cards[x="104.1"] { animation-delay: 90ms; }
use.Cards[y="228.5"], use.Cards[x="207.1"] { animation-delay: 180ms; }

/* A soft glow on the live gauge arc and on the run's progress line, so the eye has
   something to follow while the needle sweeps. The arc has stroke-opacity 0 at rest, so
   there is no glow until a run actually starts. */
.main-GaugeBlue {
  filter: drop-shadow(0 0 6px rgba(0, 80, 215, 0.45));
}
.intro-Progress,
.progressElmstart {
  filter: drop-shadow(0 0 4px rgba(0, 80, 215, 0.4));
}

/* Start button hover feedback. A scale transform is NOT used here: on an SVG <use> that
   references a symbol whose content is larger than the use box, transform-box: fill-box
   with transform-origin: center resolves to the wrong point, so the button visibly shifts
   about 25px down and right on hover and becomes hard to click. The drop-shadow glow
   keeps the affordance without moving it. */
#startButtonDesk, #startButtonMob {
  transition: filter 160ms ease-out;
}
#startButtonDesk:hover, #startButtonMob:hover {
  filter: drop-shadow(0 4px 12px rgba(0, 80, 215, 0.45));
}

/* OVH: the waiting state of the two desktop result cards. The vendor fills #graphc1 and
   #graphc2 during a run; before that the box is empty and the card reads as a hole with a
   number at one end. The placeholder lives in the intro group in app.svg, so the vendor
   hiding the intro at the start of a run is what removes it and nothing here has to watch
   for that. The box keeps the reserved chart area and breathes softly, and the
   "Waiting for test" message inside it, along with the greying of the whole results
   strip, comes from the .idleResults block just below. Colours follow the card it sits
   on: a step down from .Cards in the light skin, and the page ground in darkmode.css. */
.graphIdle rect {
  fill: #e6e6e9;
}
@keyframes graphIdlePulse {
  0%, 100% { opacity: 0.45; }
  50%      { opacity: 1; }
}
.graphIdle { animation: graphIdlePulse 2.4s ease-in-out infinite; }

/* The results strip while nothing has been measured yet: one disabled control, in grey,
   with an explicit message in each speed card's reserved chart area, to the right of the
   DOWNLOAD and UPLOAD captions. The --rc* custom properties are read by the var() forms
   above (.Cards, .rtext, .rtextnum, .rtextmbms, .Symbol, .pingIc). CSS cannot reach the
   cloned content inside a <use> shadow tree with a descendant selector, but inherited
   custom properties can, which is what makes per-skin greying possible at all. The tokens
   sit on the wrapper's light-skin values here and on the dark ones in darkmode.css, and
   the wrapper itself is gone from the DOM the moment a run starts, so normal colours
   return with the UI group. */
#intro-Desk .idleResults {
  --rcCard: #efefef;
  --rcCaption: #a3a0a0;
  --rcValue: #b3b0b0;
  --rcUnit: #bfbcbc;
  --rcArrow: #c6c7cd;
  --rcIcon: #c6c7cd;
}
.idleMsg {
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.2px;
  fill: #6e6969;   /* ≥4.5:1 on the #e6e6e9 box it sits in */
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

@media (prefers-reduced-motion: reduce) {
  use.Cards { animation: none; }
  .graphIdle { animation: none; }
  .main-GaugeBlue, .intro-Progress, .progressElmstart { filter: none; }
  #startButtonDesk, #startButtonMob { transition: none; }
  /* The finished-state blur is a look and not motion, so it stays; only its fade-in
     goes. */
  .openSpeedtestApp.ost-finished { transition: none; }
}

/* OVH: the finished state, where the WHOLE widget blurs behind one sharp control. The
   2026-09-11 version blurred nine named dial parts and kept the "Finished" label sharp,
   so a geometry-pinned button could sit under it; that pairing died in Firefox, where
   the pinning math (see speedtest.html) was Chromium-exact only. The full-frame blur
   needs no names and no measurements: one rule on the SVG root, which gains its own
   stacking context, so the sibling button stays sharp above it. The results are still
   legible through 6px of blur, and the point of the state is "done, one click from a new
   run" rather than the numbers, which the parent page carries. Re-apply on OpenSpeedTest
   upgrade. */
.openSpeedtestApp.ost-finished {
  filter: blur(6px);
  transition: filter 400ms ease-out;
}

/* OVH: the finished-state overlay. It appears a few seconds after a run ends (see
   OST_OVERLAY_DELAY_MS in speedtest.html), the widget blurs out behind it, and a centred
   card carries the result summary, a Copy-results button and Start new test. The card is
   plain HTML centred by flexbox, so it is the same pixels in every engine, which is why
   it replaced the 2026-09-10/13 label-geometry pinning that was Chromium-exact and wrong
   in Firefox. Re-apply on OpenSpeedTest upgrade. */
.ost-overlay {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(255, 255, 255, 0.35);
}
.ost-overlay[hidden] {
  display: none;
}
.ost-card {
  box-sizing: border-box;   /* min/max-width must include the padding — at a
                               320px frame the card is otherwise 336px wide */
  background: rgba(255, 255, 255, 0.96);
  color: #14161f;
  border-radius: 12px;
  padding: 22px 28px 24px;
  min-width: 280px;
  max-width: min(92vw, 420px);
  box-shadow: 0 10px 34px rgba(0, 14, 156, 0.22);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  text-align: center;
}
.ost-card-title {
  margin: 0 0 16px;
  font-size: 16px;
  font-weight: 700;
  color: #000e9c;
}
.ost-results {
  margin: 0 0 20px;
  display: grid;
  grid-template-columns: auto auto;
  justify-content: center;
  column-gap: 26px;
  row-gap: 8px;
  text-align: left;
}
.ost-row {
  display: contents;   /* let dt/dd sit in the two grid columns */
}
.ost-row dt {
  font-size: 13px;
  font-weight: 600;
  color: #5b6072;
}
.ost-row dd {
  margin: 0;
  font-size: 15px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: #14161f;
}
.ost-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  justify-content: center;
}
/* Secondary (copy) button: ODS outlined look. */
.ost-copy {
  font-family: inherit;
  font-size: 14px;
  font-weight: 600;
  color: #0050d7;
  background: transparent;
  border: 2px solid #0050d7;
  border-radius: 8px;
  padding: 8px 18px;
  cursor: pointer;
  transition: background 160ms ease-out, color 160ms ease-out;
}
.ost-copy:hover { background: #0050d7; color: #fff; }
.ost-copy:active { background: #00185e; border-color: #00185e; color: #fff; }
/* Copy feedback line under the buttons. */
.ost-copied {
  margin: 14px 0 0;
  font-size: 13px;
  font-weight: 600;
  color: #2b8000;   /* ODS success-500 */
}
.ost-copied[hidden] { display: none; }

/* OVH: the "Start new test" primary button, now a normal flow element inside the overlay
   card's actions row, where it used to be position:fixed and centred by itself. Colours
   follow the ODS primary button ramp (bg primary-500, hover 700, active 800; radius =
   theme 8px). Re-apply on OpenSpeedTest upgrade. */
.start-new-test {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  font-size: 15px;
  font-weight: 600;
  color: #fff;
  background: #0050d7;
  border: none;
  border-radius: 8px;
  padding: 10px 22px;
  cursor: pointer;
  box-shadow: 0 4px 14px rgba(0, 80, 215, 0.35);
  transition: background 160ms ease-out, transform 160ms ease-out;
}
.start-new-test:hover {
  background: #000e9c;
  transform: scale(1.04);
}
.start-new-test:active {
  background: #00185e;
  transform: scale(0.97);
}
