/* Teras Core. One stylesheet, no framework, no webfont, no JavaScript.

   Built to design-md's Tesla system (VoltAgent/awesome-design-md,
   design-md/tesla/DESIGN.md): radical subtraction. No shadows, no gradients,
   no borders around cards, no uppercase, two font weights, one chromatic
   colour, and one message per screen.

   Universal Sans is proprietary, so this uses the fallback the spec itself
   names. That also keeps this site's own rule: a webfont is a network round
   trip before the first sentence can be read. */

/* ── Tokens ────────────────────────────────────────────────────────────── */

:root {
  color-scheme: light;

  --blue: #3e6ae1;
  --blue-deep: #3457c5;        /* hover only: the spec asks for "subtle darkening" */
  --white: #ffffff;
  --ash: #f4f4f4;
  --carbon: #171a20;
  --graphite: #393c41;
  --pewter: #5c5e62;
  --cloud: #eeeeee;

  --s1: 0.5rem;
  --s2: 1rem;
  --s3: 1.5rem;
  --s4: 2rem;
  --s6: 3rem;
  --s8: 4rem;

  --rail: 15.5rem;             /* the sidebar */
  --wrap: 60rem;               /* the measure of the content beside it */
  --ease: 0.33s;
  /* One easing for everything that moves. Decelerating, never a bounce: a
     corporate page that springs reads as a template. */
  --out: cubic-bezier(0.16, 1, 0.3, 1);
}

* { box-sizing: border-box; }

html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--white);
  color: var(--graphite);
  font: 400 14px/1.43 "Universal Sans Text", -apple-system, BlinkMacSystemFont,
        "Segoe UI", Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
}

img { max-width: 100%; height: auto; display: block; }
a { color: inherit; }
p { margin: 0; }

h1, h2, h3 {
  color: var(--carbon);
  font-weight: 500;            /* the spec's ceiling: no bold, no light */
  letter-spacing: normal;
  margin: 0;
}

:focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 3px;
  border-radius: 2px;
}

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  background: var(--carbon);
  color: var(--white);
  padding: var(--s1) var(--s2);
  font-weight: 500;
  text-decoration: none;
}
.skip:focus { left: 0; }

/* ── Shell: sidebar beside content ─────────────────────────────────────── */

/* One grid for the whole document. Below the breakpoint it collapses to a
   single column and the rail becomes an ordinary top bar, which is how the
   nav stays reachable on a phone without a drawer: a drawer needs
   JavaScript, and this site ships none. */
.shell { display: grid; grid-template-columns: 1fr; }
@media (min-width: 64rem) {
  .shell { grid-template-columns: var(--rail) minmax(0, 1fr); }
}

/* Mobile: one compact bar, brand on the left and a Menu disclosure on the
   right, whose panel drops full width beneath it. Eight links wrapped into
   the bar itself took a third of a phone screen before any content, which
   is what this replaces.

   <details> rather than a script, a checkbox posing as a button, or a
   duplicated menu: it is the toggle HTML already provides, it is keyboard
   operable, and screen readers announce its state. This site ships no
   JavaScript and does not need to for a menu. */
.rail {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s2);
  background: var(--white);
  border-bottom: 1px solid var(--cloud);
  padding: var(--s2) var(--s3);
}

/* An icon, not the word "Menu" in a bordered box. The bars become a cross
   when the panel is open, which is the affordance a phone reader expects.
   The accessible name comes from the visually hidden span: an icon-only
   control with no name is a button a screen reader can only call "button". */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.rail-toggle {
  list-style: none;              /* the default disclosure triangle */
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px; height: 44px;     /* a full tap target, not just the glyph */
  margin-right: -10px;           /* optical: the icon sits on the text edge */
  border-radius: 4px;
  color: var(--carbon);
  position: relative;
  z-index: 50;                   /* above the panel, so the cross is tappable */
  transition: background-color var(--ease);
}
.rail-toggle::-webkit-details-marker { display: none; }
.rail-toggle:hover { background: var(--ash); }
.icon-close { display: none; }
.rail-menu[open] .icon-bars { display: none; }
.rail-menu[open] .icon-close { display: block; }

/* Full screen, not a dropdown strip. Tesla's own phone menu takes the whole
   viewport with large left-aligned links, and it is the better answer here
   too: eight destinations in a short panel is a cramped list, and the page
   behind it is not the thing being read. */
.rail-menu > .rail-nav {
  position: fixed;
  inset: 0;
  z-index: 40;
  background: var(--white);
  padding: 5rem var(--s3) var(--s4);
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: var(--s3);
}
.rail-menu > .rail-nav .nav a {
  font-size: 22px;
  font-weight: 500;
  min-height: 56px;              /* generous, because this is the whole screen */
  padding: 8px 4px;
}
.rail-menu > .rail-nav .nav-legal a { font-size: 17px; min-height: 44px; }
.rail-menu > .rail-nav .nav-group-label { padding-left: 4px; }

.brand {
  display: inline-flex;
  align-items: center;
  gap: var(--s1);
  text-decoration: none;
  color: var(--carbon);
}
.brand img { width: 22px; height: 22px; }
/* Tesla sets its wordmark in spaced capitals. A wordmark, not a UI label,
   so the spec's rule against uppercase interface text still holds. */
.brand-name {
  font-size: 15px;
  font-weight: 500;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  white-space: nowrap;
}

.nav { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.nav a {
  padding: 8px 12px;
  min-height: 40px;              /* a comfortable tap target on a phone */
  display: flex;
  align-items: center;
  border-radius: 4px;
  color: var(--carbon);
  text-decoration: none;
  font-size: 14px;
  font-weight: 500;
  transition: color var(--ease), background-color var(--ease);
}
.nav a:hover { background: var(--ash); }
.nav a[aria-current="page"] { background: var(--ash); }

.nav-group-label {
  font-size: 12px;
  font-weight: 500;
  color: var(--pewter);
  padding: var(--s1) 12px 4px;
}
.nav-legal a { color: var(--pewter); }

.rail-foot { font-size: 13px; color: var(--pewter); padding: var(--s2) 12px 0; border-top: 1px solid var(--cloud); }
.rail-foot a { color: var(--pewter); text-decoration: none; }
.rail-foot a:hover { color: var(--carbon); text-decoration: underline; text-underline-offset: 0.25em; }

/* Desktop: the same markup becomes a sidebar. The summary goes and the
   panel is reopened by CSS.

   Two declarations do that job, because one engine's way of hiding closed
   content is not another's: ::details-content is the specified route, and
   the child override catches engines that predate it. If an engine honours
   neither, the failure is a collapsed sidebar with a visible Menu button,
   which still works. */
@media (min-width: 64rem) {
  .rail {
    position: sticky;
    top: 0;
    height: 100vh;
    display: block;
    border-bottom: 0;
    border-right: 1px solid var(--cloud);
    padding: var(--s4) var(--s3);
    overflow-y: auto;
  }
  .rail-top { margin-bottom: var(--s6); }
  .rail-toggle { display: none; }
  .rail-menu::details-content { content-visibility: visible; block-size: auto; }
  .rail-menu > .rail-nav {
    position: static;
    display: flex;
    padding: 0;
    overflow: visible;
    gap: var(--s4);
  }
  .rail-menu > .rail-nav .nav a { font-size: 14px; min-height: 32px; padding: 6px 12px; }
  .rail-menu > .rail-nav .nav-legal a { font-size: 14px; min-height: 32px; }
  .rail-menu > .rail-nav .nav-group-label { padding: var(--s1) 12px 4px; }
  .rail-foot { border-top: 0; padding-left: 12px; }
  .nav a { min-height: 32px; padding: 6px 12px; }
}
@supports (height: 100svh) {
  @media (min-width: 64rem) { .rail { height: 100svh; } }
}

.wrap { width: 100%; max-width: var(--wrap); margin: 0 auto; padding: 0 var(--s3); }
@media (min-width: 64rem) { .wrap { padding: 0 var(--s6); margin: 0; } }

/* ── Sections ──────────────────────────────────────────────────────────── */

/* Viewport-height sections are the spec's gallery device: one product, one
   message, one screen. They are used for the hero, the product showcases and
   the closing calls to action, and NOT for prose: a short block centred in a
   tall box leaves space above it that reads as a layout fault rather than as
   deliberate silence. */
.screen {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: var(--s8) 0;
}
@supports (min-height: 100svh) { .screen { min-height: 100svh; } }
.screen--short { min-height: 60vh; }
@supports (min-height: 100svh) { .screen--short { min-height: 60svh; } }

.band { padding: var(--s8) 0; }
@media (min-width: 48rem) { .band { padding: 5rem 0; } }

.ash { background: var(--ash); }
.center { text-align: center; }
.center .lede, .center .facts, .center .measure { margin-left: auto; margin-right: auto; }

/* 40px is the spec's ceiling: "Don't use text larger than 40px on the web." */
h1 { font-size: clamp(28px, 4.4vw, 40px); line-height: 1.2; }
h2 { font-size: clamp(22px, 3vw, 30px); line-height: 1.25; }
h3 { font-size: 17px; line-height: 1.3; }

.lede { margin-top: var(--s2); font-size: 17px; line-height: 1.5; max-width: 34em; }
.measure { max-width: 42em; }
.note { color: var(--pewter); }
.prose > * + * { margin-top: var(--s2); }
.prose { max-width: 42em; }
.prose h2 { margin-top: var(--s6); }
.prose h3 { margin-top: var(--s4); }

.kicker { font-size: 14px; font-weight: 500; color: var(--pewter); margin-bottom: var(--s1); }

/* ── Buttons and links ─────────────────────────────────────────────────── */

.actions { display: flex; flex-wrap: wrap; gap: var(--s2); margin-top: var(--s4); }
.center .actions { justify-content: center; }

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 200px;
  min-height: 40px;
  padding: 4px 16px;
  border-radius: 4px;          /* barely perceptible, never a pill */
  font-size: 14px;
  font-weight: 500;
  text-decoration: none;
  border: 1px solid transparent;
  transition: background-color var(--ease), color var(--ease), border-color var(--ease);
}
.btn-primary { background: var(--blue); color: var(--white); }
.btn-primary:hover { background: var(--blue-deep); }
.btn-secondary { background: var(--white); color: var(--graphite); border-color: var(--cloud); }
.btn-secondary:hover { background: var(--ash); }
.ash .btn-secondary { background: transparent; }
.ash .btn-secondary:hover { background: var(--white); }

.link { color: var(--pewter); font-size: 14px; text-decoration: none; transition: color var(--ease); }
.link:hover { color: var(--carbon); text-decoration: underline; text-underline-offset: 0.25em; }
.links { display: flex; flex-wrap: wrap; gap: var(--s3); margin-top: var(--s2); }
.center .links { justify-content: center; }

/* ── Media slots ───────────────────────────────────────────────────────── */

/* Where a photograph or a screen recording belongs. The Tesla system is
   photography-first ("the product IS the design") and this site has none
   yet, so each slot is marked rather than left as an empty gap.

   Deliberately quiet: this is a live site that Dun & Bradstreet and Apple
   will both look at, and a loud TODO box reads as unfinished. It is an ash
   panel with a caption, which passes as a design element until the real
   asset lands. Replacing one is a straight swap for an <img> or <video>;
   docs/assets-needed.md carries the exact dimensions for each. */
.media {
  position: relative;
  background: var(--ash);
  border-radius: 12px;
  overflow: hidden;
  margin-top: var(--s4);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--s4);
  text-align: center;
}
/* Always the opposite of the section it sits in, or an ash slot inside an
   ash band is invisible and its caption floats in the open. Once a real
   <img> fills it the background is covered either way. */
.ash .media { background: var(--white); }

.media--wide { aspect-ratio: 21 / 9; }
.media--video { aspect-ratio: 16 / 9; }
.media--square { aspect-ratio: 1 / 1; }
.media--tall { aspect-ratio: 4 / 5; }
.media p { color: var(--pewter); font-size: 13px; max-width: 28em; }
.media b { display: block; color: var(--carbon); font-weight: 500; margin-bottom: 4px; }
.media img, .media video { width: 100%; height: 100%; object-fit: cover; border-radius: 12px; }

/* ── Blocks ────────────────────────────────────────────────────────────── */

.grid { display: grid; gap: var(--s6) var(--s4); margin-top: var(--s6); grid-template-columns: 1fr; }
@media (min-width: 48rem) { .grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 60rem) { .grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.grid > * { min-width: 0; }
.grid h3 { margin-bottom: var(--s1); }

.list { margin: var(--s2) 0 0; padding: 0; list-style: none; }
.list li { padding: 6px 0; }

/* A definition row. The rule here separates records rather than decorating a
   container, which is the one job the spec leaves a line. */
.facts { margin: var(--s4) 0 0; padding: 0; list-style: none; max-width: 42em; }
.facts li { padding: var(--s2) 0; border-top: 1px solid var(--cloud); }
.facts li:last-child { border-bottom: 1px solid var(--cloud); }
.facts b { display: block; font-weight: 500; color: var(--carbon); }
.center .facts { text-align: left; }

.steps { margin: var(--s6) 0 0; padding: 0; list-style: none; counter-reset: step; }
.steps li { counter-increment: step; padding-left: 2.5rem; position: relative; max-width: 42em; }
.steps li + li { margin-top: var(--s4); }
.steps li::before {
  content: counter(step);
  position: absolute; left: 0; top: 0;
  color: var(--pewter); font-size: 14px; font-weight: 500;
}
.steps h3 { margin-bottom: 4px; }

.showcase { display: flex; flex-direction: column; align-items: center; text-align: center; }
.showcase-mark { width: 88px; height: 88px; border-radius: 12px; }
.showcase h2 { margin-top: var(--s3); }
.showcase .lede { max-width: 30em; }
.showcase-soon { margin-top: var(--s2); color: var(--pewter); }

/* ── Footer ────────────────────────────────────────────────────────────── */

.site-footer { border-top: 1px solid var(--cloud); padding: var(--s6) 0 var(--s4); }
.footer-inner { display: grid; gap: var(--s4); grid-template-columns: 1fr; }
@media (min-width: 48rem) { .footer-inner { grid-template-columns: 1.6fr 1fr 1fr; } }
.footer-legal { margin-top: var(--s2); color: var(--pewter); }
.footer-heading { font-size: 14px; font-weight: 500; color: var(--carbon); margin-bottom: var(--s1); }
.footer-nav { display: flex; flex-direction: column; gap: 6px; align-items: flex-start; }
.footer-nav a { color: var(--pewter); text-decoration: none; transition: color var(--ease); }
.footer-nav a:hover { color: var(--carbon); text-decoration: underline; text-underline-offset: 0.25em; }
/* Pewter outright, not a faded copy of it: opacity over grey is how an
   earlier version of this file landed at 3.18:1 against a 4.5 requirement. */
.footer-soon { color: var(--pewter); }

.footer-base {
  display: flex; flex-wrap: wrap; justify-content: space-between;
  gap: var(--s1) var(--s3);
  margin-top: var(--s4); padding-top: var(--s3);
  border-top: 1px solid var(--cloud); color: var(--pewter);
}

.notfound { text-align: center; align-items: center; }
.notfound img { width: 56px; height: 56px; margin: 0 auto var(--s3); }

/* A second heading inside one footer column needs air above it. A class
   rather than an inline style, so the value stays with the rest of them. */
.footer-heading--gap { margin-top: var(--s3); }

/* ── Product screenshots ───────────────────────────────────────────────── */

/* A real capture of the real product, framed at its OWN aspect ratio.

   Not the cropping `.media` slot above, and that is the whole decision: every
   one of these is a screen where the interesting half sits at an edge - the
   sidebar naming the modules, the totals row under a payroll run, the four
   transfers across the top. `object-fit: cover` in a 16:9 box eats exactly
   those. So the <img> carries its own width and height, the box takes the
   ratio from them, and nothing is cropped.

   The hairline is the one border on this site that surrounds a card, and it
   earns it: several of these screenshots are white to the edge, and without
   it the app's own chrome bleeds into the page and the picture stops reading
   as a screen. */
.shot { margin: var(--s4) 0 0; }
.shot img {
  width: 100%;
  height: auto;
  border-radius: 12px;
  border: 1px solid var(--cloud);
  background: var(--white);
}
.shot figcaption {
  margin-top: var(--s2);
  color: var(--pewter);
  font-size: 13px;
  max-width: 42em;
}

/* ── What each product is best at ──────────────────────────────────────── */

/* Two or three per product, taken from the product's own repository rather
   than written here: the wording of a claim about EPF tables or a payment
   gateway has to come from the team that implemented it, or this site starts
   promising things nobody shipped. */
.killers { margin: var(--s6) 0 0; padding: 0; list-style: none; }
.killers > li + li { margin-top: var(--s4); }
.killers h3 { margin-bottom: 4px; }
.killers p { max-width: 42em; }
@media (min-width: 60rem) {
  .killers { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--s4); }
  .killers > li + li { margin-top: 0; }
}

/* ── Motion ────────────────────────────────────────────────────────────── */

/* Every rule below is inside `prefers-reduced-motion: no-preference`, so a
   reader who has asked their operating system for less gets the same page
   with nothing moving: the montage holds its first frame and everything else
   is simply drawn. That is the whole accessibility story here, and it is why
   none of this is done in JavaScript - a script has to remember to ask, and
   CSS cannot forget. */

/* The hero montage. Four screenshots, one per product, cross-fading in a
   32-second loop: eight seconds each, about a second of that spent fading.
   Absolutely positioned into one box so the four occupy one space; the box
   takes its height from the ratio rather than from the images, because an
   absolutely positioned child contributes none.

   The first frame is opaque by default and animated on top of that, so a
   browser that runs no animation at all still shows a picture rather than an
   empty grey panel. */
/* The wrapper carries the margin and the caption, because the box below is a
   containing block for four absolutely positioned images and a <figcaption>
   inside it would be laid out over them.

   The margin is written out rather than left to the browser, and that is not
   tidiness: a bare <figure> defaults to `margin: 1em 40px`, so on a phone the
   hero picture sat 40px in from both edges while every other element on the
   page ran to the measure. It read as a stray image somebody had dropped in,
   which is exactly what it looked like. */
.hero-shots { margin: var(--s4) 0 0; }
.hero-shots figcaption { margin-top: var(--s2); color: var(--pewter); font-size: 13px; }

/* Hairline and ash, the same frame the product figures get. A desktop screen
   shrunk to a phone's width cannot be read whatever is done to it, so the job
   here is to make it read as a deliberate panel of product screens rather
   than as a photograph that lost its caption. The frame does half of that and
   the caption does the other half. */
.montage {
  position: relative;
  aspect-ratio: 16 / 10;
  border-radius: 12px;
  overflow: hidden;
  border: 1px solid var(--cloud);
  background: var(--ash);
}
.montage img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top left;
  opacity: 0;
  border-radius: 12px;
}
.montage img:first-child { opacity: 1; }

@media (prefers-reduced-motion: no-preference) {
  .montage img { animation: montage 32s linear infinite; }
  .montage img:nth-child(1) { animation-delay: 0s; }
  .montage img:nth-child(2) { animation-delay: 8s; }
  .montage img:nth-child(3) { animation-delay: 16s; }
  .montage img:nth-child(4) { animation-delay: 24s; }

  .rise { animation: rise 0.6s var(--out) both; }
  .rise-1 { animation-delay: 0.05s; }
  .rise-2 { animation-delay: 0.15s; }
  .rise-3 { animation-delay: 0.25s; }
}

/* 3% of 32s is under a second of fade, and 25% is the slot: the fourth frame
   leaves exactly as the first arrives. `opacity: 1` at 0% for the first child
   is carried by the rule above rather than by the keyframes, so the loop can
   start from zero for all four. */
@keyframes montage {
  0%   { opacity: 0; }
  3%   { opacity: 1; }
  22%  { opacity: 1; }
  25%  { opacity: 0; }
  100% { opacity: 0; }
}

@keyframes rise {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}

/* Scroll-driven reveal, and it is deliberately additive-only: the element is
   fully visible without it, and the animation is a scroll-linked fade the
   browser drops if it cannot do it. Chromium runs it today, Safari and
   Firefox do not, and the page reads identically either way - which is the
   only shape this is worth having in.

   It is aimed at figures and lists, never at a paragraph or a heading. A
   scroll timeline that misbehaved would take an image off the screen, and
   that is recoverable; sentences that never fade in are a broken page. */
/* `screen` as well as the motion query: a scroll timeline has no progress to
   read when there is no scrolling, so on paper (and in a full-page capture,
   which is how this was first noticed) every revealed figure would print at
   its opening keyframe, which is opacity 0. */
@supports (animation-timeline: view()) {
  @media screen and (prefers-reduced-motion: no-preference) {
    .reveal {
      /* Longhands, never the `animation` shorthand. The shorthand resets
         animation-duration to 0s, and a scroll-driven animation wants it left
         at `auto` so its length comes from the range. Chromium happens to
         run the shorthand correctly today (checked, both spellings reach
         opacity 1 in view), so this is written to the spec rather than to one
         engine's forgiveness: a 0s duration is the one way this rule could
         leave an element stuck at its opening keyframe, which is opacity 0. */
      animation-name: rise;
      animation-timing-function: linear;
      animation-fill-mode: both;
      animation-timeline: view();
      animation-range: entry 5% cover 20%;
    }
  }
}
