/* Design tokens transcribed from the TopRacer Console stylesheets so the site
   and the application read as one product. See design spec section 3.

   --acc is for borders, hover, and selected states only. It measures 2.9:1 on
   white and must never carry text; --acc-text is the same green darkened to
   4.8:1 for anything readable. */
:root{
  --surface:#ffffff;
  --fg:#1b2530;
  --mut:#4a5a55;
  --cap:#6f7d77;
  --acc:#1f8c6b;
  --acc-text:#1a6b53;
  --acc-strong:#14523f;
  /* These three were the reason the page read as washed out, and it was not
     the accent. --bd sat at 1.2:1 on white, so card edges and table rules were
     barely there; --band at 1.06:1 meant a "banded" section looked identical
     to the one above it. Deepened until the boundaries are legible while the
     hue stays where it was, so the site still matches the application. */
  --bd:#bccdc6;
  --hov:#eef7f3;
  --band:#e2ebe7;
  --sel:#d8ece3;
  --chip:#cfe8dd;
  --err:#a33c26;        /* 6.5:1 on white, for the failure message */
  --err-bd:#b4472f;     /* the invalid-field border; 5.4:1, non-text */

  /* One colour per stage of the loop. Four identical green discs read as the
     same thing happening four times; four hues say these are different kinds
     of work. Each is dark enough to carry white numerals, and they run warm to
     cool around the ring so the direction is legible even in greyscale. */
  --loop-1:#a85820;     /* design    -- amber  */
  --loop-2:#1f7a8c;     /* train     -- teal   */
  --loop-3:#1a7a5d;     /* deploy    -- green  */
  --loop-4:#6b4c9a;     /* evaluate  -- violet */
  --r-input:6px;
  --r-btn:8px;
  --r-card:10px;

  /* One spacing scale, so vertical rhythm is a choice from a list rather than a
     number invented at each call site. */
  --sp-1:.35rem; --sp-2:.7rem; --sp-3:1.1rem;
  --sp-4:1.8rem; --sp-5:3rem;  --sp-6:4.5rem;

  /* Reading measure. Lines longer than this are hard to track back to the next
     one, and CJK runs wider per character than the ch unit suggests. */
  --measure:60ch;
}

*{box-sizing:border-box}

/* The header is sticky, so an anchor jump has to stop short of it or the
   heading it landed on sits underneath. */
html{scroll-behavior:smooth;scroll-padding-top:5rem}
@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  *,*::before,*::after{animation-duration:.01ms!important;animation-iteration-count:1!important;
    transition-duration:.01ms!important}
}

body{
  margin:0;
  background:var(--surface);
  color:var(--fg);
  /* Same stack as the app: no web font, so rendering matches and no CJK file
     is downloaded three times over. */
  font:16px/1.7 -apple-system,BlinkMacSystemFont,"Segoe UI","PingFang TC",
       "Microsoft JhengHei","Noto Sans TC",sans-serif;
  -webkit-font-smoothing:antialiased;
}

/* Keyboard users must be able to jump the navigation. */
.skip{position:absolute;left:-9999px;top:0;background:var(--surface);color:var(--fg);
  padding:.7rem 1.1rem;border:1px solid var(--acc);border-radius:var(--r-btn);z-index:100}
.skip:focus{left:1rem;top:1rem}

:focus-visible{outline:2px solid var(--acc-text);outline-offset:2px}

.wrap{max-width:1080px;margin:0 auto;padding:0 1.4rem}

/* ---- chrome ---------------------------------------------------------- */
.site-header{border-bottom:1px solid var(--bd);background:var(--surface);
  position:sticky;top:0;z-index:20}
.site-header .wrap{display:flex;align-items:center;gap:var(--sp-3);min-height:64px;
  flex-wrap:wrap}
.brand{font-size:1.25rem;font-weight:700;color:var(--fg);text-decoration:none;
  margin-right:auto}
.brand span{color:var(--acc-text)}

.site-nav{display:flex;gap:var(--sp-1)}
.site-nav a{display:inline-flex;align-items:center;min-height:44px;padding:.4rem .8rem;
  border-radius:var(--r-btn);color:var(--fg);text-decoration:none;font-size:.95rem}
.site-nav a:hover{background:var(--hov)}
.site-nav a[aria-current="page"]{color:var(--acc-text);font-weight:600}

/* --- the disclosure, below the breakpoint ---
   Full-width row of its own under the brand, opening downward. */
.nav-disclosure{order:3;flex-basis:100%}
.nav-disclosure > summary{display:inline-flex;align-items:center;gap:var(--sp-2);
  min-height:44px;padding:.4rem .8rem;margin:0 -.8rem;border-radius:var(--r-btn);
  color:var(--fg);font-size:.95rem;font-weight:600;cursor:pointer;
  list-style:none}                       /* Firefox: kill the disclosure triangle */
.nav-disclosure > summary::-webkit-details-marker{display:none}   /* Safari */
.nav-disclosure > summary:hover{background:var(--hov)}
.nav-disclosure[open] > summary{color:var(--acc-text)}
/* The open menu lives inside a sticky header, so on a short screen -- a phone in
   landscape -- the last items would sit off the bottom with no way to reach
   them. Scroll the list instead. */
.nav-disclosure .site-nav{flex-direction:column;align-items:stretch;
  padding:var(--sp-2) 0 var(--sp-3);max-height:65dvh;overflow-y:auto}
.nav-disclosure .site-nav a{padding:.55rem .8rem;margin:0 -.8rem}

.lang-switch{display:flex;gap:.35rem}
.lang-switch a{display:inline-flex;align-items:center;justify-content:center;
  min-width:44px;min-height:44px;padding:.3rem .6rem;border:1px solid var(--bd);
  border-radius:var(--r-btn);color:var(--fg);text-decoration:none;font-size:.9rem}
.lang-switch a:hover{border-color:var(--acc);background:var(--hov)}
.lang-switch a[aria-current="true"]{border:2px solid var(--acc);background:var(--sel);
  color:var(--acc-text);font-weight:700}

/* --- the same markup, above the breakpoint ---
   840px is where the English navigation stops fitting beside the brand and the
   language switcher; Chinese fits sooner but one breakpoint has to serve all
   three locales, so it is set by the widest.

   display:contents dissolves the <details> box and lets the links join the
   header's flex row. Two engines have to be told to stop hiding them: older
   ones apply `display:none` to a closed details' children, which the author
   rule below outranks, and Chrome hides ::details-content with
   content-visibility, which needs its own override. */
@media (min-width:840px){
  .nav-disclosure{display:contents}
  .nav-disclosure > summary{display:none}
  .nav-disclosure .site-nav{display:flex;flex-direction:row;padding:0;
    max-height:none;overflow:visible}
  .nav-disclosure .site-nav a{margin:0}
  .nav-disclosure::details-content{content-visibility:visible;display:contents}
}

.site-footer{border-top:1px solid var(--bd);padding:var(--sp-5) 0;
  color:var(--mut);font-size:.92rem}
.site-footer a{color:var(--acc-text)}

/* ---- content --------------------------------------------------------- */
/* Sections carry the vertical rhythm, not main, so a banded section's colour
   reaches the edge of the viewport and touches its neighbours. */
.section{padding:var(--sp-5) 0}
.band{background:var(--band)}
.band + .band{padding-top:0}

h1{font-size:clamp(1.9rem,5vw,3rem);line-height:1.2;margin:0 0 var(--sp-2);
  font-weight:700;max-width:var(--measure)}
h2{font-size:clamp(1.35rem,3vw,1.75rem);line-height:1.3;margin:0 0 var(--sp-2);
  font-weight:600}
h3{font-size:1.1rem;line-height:1.4;margin:0 0 var(--sp-2);font-weight:600}
/* Body text is --fg, not --mut. Setting every paragraph in grey was the other
   half of why the page read as weak: it is not a contrast failure by the
   numbers, but page after page of grey reads as faded. --mut is now for text
   that really is secondary -- hints, captions, notes. */
p{margin:0 0 var(--sp-3);color:var(--fg);max-width:var(--measure)}
.lead,.summary,.hint,.price-note,.disclaimer,.kind{color:var(--mut)}
.lead{font-size:1.1rem}
/* Links inside prose. Scoped to paragraphs so it cannot reach the buttons and
   card titles, which carry their own colour. */
p a{color:var(--acc-text)}
p a:hover{text-decoration:none}

.btn{display:inline-flex;align-items:center;gap:.5rem;min-height:44px;
  padding:.65rem 1.3rem;border-radius:var(--r-btn);font-weight:600;font-size:.95rem;
  text-decoration:none;cursor:pointer;border:1px solid var(--bd);
  background:var(--surface);color:var(--fg);transition:background .15s,border-color .15s}
.btn:hover{border-color:var(--acc);background:var(--hov)}
.btn-primary{background:var(--acc-text);border-color:var(--acc-text);color:#fff}
.btn-primary:hover{background:var(--acc-strong);border-color:var(--acc-strong)}
.btn-row{display:flex;gap:var(--sp-2);flex-wrap:wrap;margin-top:var(--sp-4)}

/* ---- the loop --------------------------------------------------------- */
/* Narrow: the ring on top, then the steps in order. Wide: the steps sit at the
   four compass points around the ring, which is the arrangement that says
   "this comes back to where it started" without a sentence having to. */
.loop-ring{position:relative;margin-top:var(--sp-5)}
.loop-svg{display:block;width:190px;height:auto;margin:0 auto var(--sp-4)}
.loop-svg .loop-node text{fill:#fff;font:700 19px/1 inherit;
  text-anchor:middle;dominant-baseline:central}

/* Each node and the arc leaving it share a colour, so the eye follows one hue
   handing off to the next rather than reading four identical discs. */
.loop-svg .n1 circle,.loop-num-1{fill:var(--loop-1);background:var(--loop-1)}
.loop-svg .n2 circle,.loop-num-2{fill:var(--loop-2);background:var(--loop-2)}
.loop-svg .n3 circle,.loop-num-3{fill:var(--loop-3);background:var(--loop-3)}
.loop-svg .n4 circle,.loop-num-4{fill:var(--loop-4);background:var(--loop-4)}
.loop-svg .a1{stroke:var(--loop-1)}
.loop-svg .a2{stroke:var(--loop-2)}
.loop-svg .a3{stroke:var(--loop-3)}
.loop-svg .a4{stroke:var(--loop-4)}

.loop{list-style:none;margin:0;padding:0;
  display:grid;grid-template-columns:1fr;gap:var(--sp-4)}
.loop > li{max-width:var(--measure)}
.loop h3{display:flex;align-items:center;gap:.6rem;margin:0 0 var(--sp-2)}
.loop p{margin:0;font-size:.94rem}

/* The badge beside each heading takes that stage's colour, so the ring and the
   text are read as the same four things rather than two separate lists. */
.loop-num{flex:none;display:inline-flex;align-items:center;justify-content:center;
  width:1.9rem;height:1.9rem;border-radius:50%;color:#fff;
  font-size:.9rem;font-weight:700}

.loop-note{display:flex;align-items:baseline;flex-wrap:wrap;gap:0 .5rem;
  margin-top:var(--sp-5);max-width:var(--measure)}
.loop-back{color:var(--acc-text);font-size:1.4rem;line-height:1}
.loop-back-label{color:var(--acc-text);font-weight:600}

@media (min-width:900px){
  /* The centre cell is left empty and the ring is placed into it absolutely.
     Making the <ol> itself the grid keeps the list semantics intact -- putting
     display:contents on it, or moving the SVG inside it, would each cost the
     "list of four steps" that a screen reader announces. */
  .loop-svg{position:absolute;top:50%;left:50%;
    transform:translate(-50%,-50%);width:300px;margin:0}
  .loop{grid-template-columns:1fr 340px 1fr;
    grid-template-rows:auto 340px auto;
    align-items:center;justify-items:center;gap:var(--sp-3) var(--sp-4)}
  .loop > li{max-width:26rem}
  .loop > li:nth-child(1){grid-area:1 / 2 / 2 / 3;text-align:center}
  .loop > li:nth-child(2){grid-area:2 / 3 / 3 / 4}
  .loop > li:nth-child(3){grid-area:3 / 2 / 4 / 3;text-align:center}
  .loop > li:nth-child(4){grid-area:2 / 1 / 3 / 2;text-align:right}
  .loop > li:nth-child(1) h3,
  .loop > li:nth-child(3) h3{justify-content:center}
  .loop > li:nth-child(4) h3{flex-direction:row-reverse}
  .loop-note{margin-left:auto;margin-right:auto}
}

/* ---- product cards ---------------------------------------------------- */
/* auto-fit rather than a media query: the grid reflows for four cards or for
   one, and stays correct when a product is added. */
.cards{display:grid;gap:var(--sp-3);margin-top:var(--sp-4);
  grid-template-columns:repeat(auto-fit,minmax(260px,1fr))}
.card{display:flex;flex-direction:column;border:1px solid var(--bd);
  border-radius:var(--r-card);padding:var(--sp-4);background:var(--surface)}
.card:hover{border-color:var(--acc)}
.card h3{margin:var(--sp-1) 0 var(--sp-2)}
/* Prose, not products: no border to hover, nothing to click. */
.cards-plain .card{border:none;padding:0;background:none}
.cards-plain .card:hover{border:none}
/* Cards set their own measure by being narrow; the global one would leave a
   ragged gap inside a wide card. */
.card p{font-size:.94rem;max-width:none}
/* The summary absorbs the slack, so cards of unequal text length still line
   their buttons up. It has to be this element specifically -- matching the last
   paragraph instead catches the "coming soon" pill and stretches it into an
   oval. */
.card .summary{flex:1}
/* --mut, not --cap: at this size it is normal text and needs 4.5:1. --cap
   measures 3.07:1 on white, which is a border colour, not a text colour. */
.kind{margin:0;color:var(--mut);font-size:.78rem;letter-spacing:.06em;
  text-transform:uppercase}
.card .btn-row{margin-top:1rem;gap:.5rem}
.btn-small{padding:.45rem .9rem;font-size:.88rem;min-height:38px}

/* Not a link and not a <button>: there is nothing to activate yet. A disabled
   look on a real control would still take focus and promise something. */
.state{display:inline-flex;align-items:center;align-self:flex-start;margin:1rem 0 0;
  padding:.35rem .8rem;border-radius:999px;background:var(--chip);
  color:var(--acc-text);font-size:.85rem;font-weight:600}
.state-muted{background:var(--band);color:var(--mut)}

/* ---- price ------------------------------------------------------------ */
.price{display:flex;align-items:baseline;gap:.45rem;flex-wrap:wrap;
  margin:var(--sp-3) 0 0}
.price-amount{font-size:1.35rem;font-weight:700;color:var(--fg);
  font-variant-numeric:tabular-nums}
.price-unit{color:var(--mut);font-size:.9rem}
.price-large .price-amount{font-size:2rem}
.price-note{font-size:.9rem;margin-top:.4rem}

/* Free is a fact about the product, not a call to action, so it reads as a
   label beside the category rather than as a button. */
.tag-free{display:inline-block;margin-left:.45rem;padding:.1rem .5rem;
  border-radius:999px;background:var(--chip);color:var(--acc-text);
  font-size:.72rem;font-weight:700;letter-spacing:.04em;vertical-align:.05em}

/* ---- screenshot ------------------------------------------------------- */
.shot{margin:var(--sp-4) 0 0;max-width:100%}
.shot img{display:block;width:100%;height:auto;border:1px solid var(--bd);
  border-radius:var(--r-card)}

/* ---- specifications --------------------------------------------------- */
/* A <dl>, not a <table>: this is a set of labelled values, not a grid of them.
   It also means the label can sit above its value on a phone instead of
   forcing a horizontal scrollbar. */
.specs{margin:var(--sp-4) 0 0;display:grid;gap:0;
  grid-template-columns:repeat(auto-fit,minmax(300px,1fr))}
.specs > div{display:flex;flex-direction:column;gap:.15rem;
  padding:var(--sp-3) 0;border-top:1px solid var(--bd)}
.specs dt{color:var(--mut);font-size:.82rem;letter-spacing:.05em;
  text-transform:uppercase}
.specs dd{margin:0;color:var(--fg)}

/* The re:Invent non-affiliation notice. Quiet, but not so quiet it reads as
   decoration -- it is a claim the site is legally required to make. */
.disclaimer{margin-top:var(--sp-4);padding-top:var(--sp-3);
  border-top:1px solid var(--bd);color:var(--mut);font-size:.86rem;
  max-width:var(--measure)}

/* ---- long-form text --------------------------------------------------- */
.prose h2{margin-top:var(--sp-5)}
.prose h2:first-child{margin-top:0}
.prose p{max-width:var(--measure)}

.back{display:inline-flex;align-items:center;min-height:44px;
  color:var(--acc-text);text-decoration:none;font-weight:600}
.back:hover{text-decoration:underline}

/* A heading that leads somewhere. The whole card is deliberately not a link:
   the download buttons inside it go elsewhere, and nesting those in a link is
   invalid and unusable with a keyboard. */
.card h3 a,.buy-item h3 a{color:var(--fg);text-decoration:none}
.card h3 a:hover,.buy-item h3 a:hover{color:var(--acc-text);text-decoration:underline}

/* ---- buy ------------------------------------------------------------- */
/* Each item is its own card. A single hairline between them did not work:
   every item also contains a specification table drawn with the same hairline,
   so the line that separated two products looked exactly like the lines inside
   one, and three items read as a single wall of text. A surface, a border, and
   real space between them settle where one product ends. */
.buy-item{background:var(--surface);border:1px solid var(--bd);
  border-radius:var(--r-card);padding:var(--sp-4);margin-top:var(--sp-4)}
.buy-item:first-of-type{margin-top:var(--sp-5)}
.buy-item-head{display:flex;align-items:flex-start;justify-content:space-between;
  gap:var(--sp-3);flex-wrap:wrap;padding-bottom:var(--sp-3);
  border-bottom:2px solid var(--bd)}
.buy-item-head h3{margin:var(--sp-1) 0 0;font-size:1.35rem}
.buy-item-head .price{margin:0}
.buy-item .state{margin-top:var(--sp-1)}
.buy-item > p{margin-top:var(--sp-3)}
.buy-item .specs{margin-top:var(--sp-2)}
/* The table already sits under the head rule; a second line straight after it
   would read as a double border. */
.buy-item .specs > div:first-child{border-top:none}

/* ---- forms ------------------------------------------------------------ */
.form{display:grid;gap:var(--sp-3);margin-top:var(--sp-4);max-width:44rem;
  grid-template-columns:repeat(auto-fit,minmax(240px,1fr))}
.field{display:flex;flex-direction:column;gap:.35rem}
.field-wide{grid-column:1 / -1}
.form label{font-size:.92rem;font-weight:600;color:var(--fg)}
.optional{font-weight:400;color:var(--mut)}

.form input,.form select,.form textarea{
  font:inherit;font-size:.95rem;color:var(--fg);background:var(--surface);
  border:1px solid var(--bd);border-radius:var(--r-input);padding:.6rem .75rem;
  min-height:44px;width:100%}
.form textarea{min-height:9rem;resize:vertical;line-height:1.6}
.form input:hover,.form select:hover,.form textarea:hover{border-color:var(--acc)}
.form input:focus,.form select:focus,.form textarea:focus{
  border-color:var(--acc);outline:2px solid var(--acc-text);outline-offset:1px}
/* The browser marks a required field invalid before it has been touched, so
   :invalid alone would paint every field red on arrival. :user-invalid waits
   until the visitor has actually interacted with it. */
.form input:user-invalid,.form textarea:user-invalid{border-color:var(--err-bd)}

.hint{font-size:.86rem;color:var(--mut);margin:.2rem 0 0}
.field-wide .btn{margin-top:var(--sp-3)}

/* Off-screen rather than display:none: some bots skip fields that are not
   rendered, and this one only works if they fill it in. */
.trap{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}

.form-status{margin:var(--sp-3) 0 0;min-height:1.5rem;font-weight:600}
.form-status.is-busy{color:var(--mut)}
.form-status.is-ok{color:var(--acc-text)}
.form-status.is-bad{color:var(--err)}

/* ---- the inquiry queue ------------------------------------------------ */
.tabs{display:flex;gap:var(--sp-1);flex-wrap:wrap;align-items:center;
  margin-top:var(--sp-4)}
.tab{font:inherit;font-size:.95rem;font-weight:600;cursor:pointer;
  min-height:44px;padding:.5rem 1rem;border:1px solid var(--bd);
  border-radius:var(--r-btn);background:var(--surface);color:var(--fg)}
.tab:hover{border-color:var(--acc);background:var(--hov)}
.tab[aria-selected="true"]{border:2px solid var(--acc);background:var(--sel);
  color:var(--acc-text)}
.tabs #signout{margin-left:auto}

.inquiry{margin-top:var(--sp-3)}
.inquiry-head{display:flex;justify-content:space-between;gap:var(--sp-3);
  flex-wrap:wrap}
.inquiry-head .kind{margin:0}
.inquiry h3{margin:var(--sp-1) 0 .2rem}
.inquiry-org{margin:0 0 .2rem;color:var(--mut);font-size:.9rem}
/* pre-wrap, so a message keeps the line breaks whoever wrote it put in. */
.inquiry-message{white-space:pre-wrap;margin-top:var(--sp-3);color:var(--fg)}
.inquiry .btn-row{margin-top:var(--sp-3);align-items:center}

/* Placeholder copy is visibly marked so it cannot be mistaken for final text
   during review, and so a stray one is obvious if it survives to a build. */
.placeholder{border-left:3px solid var(--cap);background:var(--band);
  padding:.7rem 1rem;border-radius:0 var(--r-btn) var(--r-btn) 0;
  color:var(--mut);font-size:.9rem}
