/* ============================================================
   Turtle Labs design system

   Ported from brandos.turtlelabs.co.in so the group site and the Studio
   carry the same surface as BrandOS rather than an approximation of it.
   Values here are the ones BrandOS actually ships: the 1240px measure, the
   96px section rhythm, the type scale, the three glow layers and the logo
   filter. Where a number looks oddly specific it is because it was copied
   rather than chosen.

   Loaded before each site's own stylesheet, which holds only the components
   that are genuinely its own.
   ============================================================ */

@import url('/assets/fonts/fonts.css');

/* ---- the brand palette ---------------------------------------------------
   Six hues, sampled from assets/logo.webp rather than chosen: crimson, red,
   amber, green, cyan and blue are the colours in the turtle, in the order the
   mark runs them, weighted by how much of the logo each one is.

   Everything that used to carry its own near-miss of these now points here.
   The site had three separate rainbows in play, the group's #C80018/#FF8C00
   set, Shell's #ec4b3b/#f5822a set and WebOS's, none of which was the logo
   and none of which matched the others.

   Each hue comes in two forms. The base is the logo colour and is for fills,
   bars and anything large. The -ink form is the same hue moved until it
   clears 4.5:1 on the grounds of its own theme, and is what text uses: the
   logo amber is 1.9:1 on white and unreadable as a label.
   ------------------------------------------------------------------------ */
/* ---- corners -------------------------------------------------------------
   Fixed pixel radii do not survive the range these sites are read at. 28px on
   a 390px phone is a quarter of the card width and reads as a lozenge; the
   same value on a 1440px card is a detail nobody notices. Each step scales
   with the viewport, so the proportion holds at both ends.
   ------------------------------------------------------------------------ */
:root {
  --r-xs:   clamp(6px,  0.5vw,  8px);
  --r-sm:   clamp(8px,  0.8vw, 12px);
  --r-md:   clamp(12px, 1.2vw, 18px);
  --r-lg:   clamp(14px, 1.5vw, 22px);
  --r-xl:   clamp(16px, 1.9vw, 28px);
  --r-pill: 999px;
}

:root {
  --tl-crimson: #D80012;
  --tl-red:     #FC3600;
  --tl-amber:   #FC9000;
  --tl-green:   #00A25A;
  --tl-cyan:    #00B4EA;
  --tl-blue:    #006CB4;

  --tl-crimson-ink: #E44D59;
  --tl-red-ink:     #FC3600;
  --tl-amber-ink:   #FC9000;
  --tl-green-ink:   #00A25A;
  --tl-cyan-ink:    #00B4EA;
  --tl-blue-ink:    #2F87C2;
  /* Three more, between the logo hues, for the surfaces with more than six
     things to colour. Same rule: a value per theme. */
  --tl-teal-ink:    #00B08C;
  --tl-indigo-ink:  #6285D2;
  --tl-violet-ink:  #9F76D2;

  /* The mark, left to right. One definition, referenced everywhere. */
  --tl-rainbow: linear-gradient(90deg, var(--tl-crimson), var(--tl-red),
    var(--tl-amber), var(--tl-green), var(--tl-cyan), var(--tl-blue));
}
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    --tl-crimson-ink: #D80012;
    --tl-red-ink:     #D82E00;
    --tl-amber-ink:   #A55E00;
    --tl-green-ink:   #007F47;
    --tl-cyan-ink:    #00799D;
    --tl-blue-ink:    #006CB4;
    --tl-teal-ink:    #007A61;
    --tl-indigo-ink:  #4B6AAD;
    --tl-violet-ink:  #7C5AA8;
  }
}
:root[data-theme="light"] {
  --tl-crimson-ink: #D80012;
  --tl-red-ink:     #D82E00;
  --tl-amber-ink:   #A55E00;
  --tl-green-ink:   #007F47;
  --tl-cyan-ink:    #00799D;
  --tl-blue-ink:    #006CB4;
  --tl-teal-ink:    #007A61;
  --tl-indigo-ink:  #4B6AAD;
  --tl-violet-ink:  #7C5AA8;
}

:root {
  --ink:246,243,236; --bg-rgb:11,11,12;
  --bg:#0B0B0C; --bg2:#141416; --card:#18181B; --text:#F6F3EC;
  --prose:#DEDAD2; --lede-c:#B8B4AE;
  --muted:#A1A1AA; --faint:#8C8C96;
  --line:rgba(var(--ink),0.12); --line2:rgba(var(--ink),0.08);
  --green:#34C759; --green-ink:#04170B; --greent:#4ADE80;
  --red:#FF6B6B; --orange:#FF8A5C; --amber:#FFB35C; --blue:#60A5FA; --cyan:#67E8F9;
  /* A FIELD THAT WAS REFUSED, AND WHY IT IS ITS OWN TOKEN.
     #E4553F was written literally in five places in tl-components.css, so the
     invalid border was the light theme's colour on a black page too. The
     signed-off accounts design carried both values and the system carried
     neither: this takes the live one for light and the design's for dark.
     Deliberately not --red. --red is a spectrum hue a section can be painted
     in; a refused field is a state. A state and a hue sharing a token is how
     a section ends up the colour of an error. */
  --invalid:#FF5C43;

  /* A client logo is usually black on white. Inverting it in the dark theme
     is what stops every mark arriving inside its own white rectangle. */
  --logo-filter: invert(1) grayscale(1) brightness(1.35);
  --header-bg: rgba(11,11,12,0.85);

  /* The venture tones moved to tl-components.css on 27 September. They
     were here and WebOS does not load this file, so every --tone-*-ink
     on /web fell back to the raw display hue and measured 1.89:1. */

  --maxw: 1240px;
  /* A MEASURE IS COUNTED IN CHARACTERS, NOT PIXELS.
     It was 720px, which is a comfortable line of 17px type and 103 characters
     of 14px type, so every small paragraph in the estate ran about a third
     too long and the legal documents ran longest of all. Nobody had set the
     wrong number: a pixel measure cannot be right for two type sizes at once,
     because the thing being limited is how far the eye travels before it has
     to find the next line, and that is characters.
     68ch is the middle of the range typography has agreed on for a century,
     and it now narrows and widens with whatever type it is given.
     Found on 29 September by verify/checks/every-page.js, on thirteen pages
     across five sites at once. */
  --measure: 68ch;
  --gap-section: clamp(72px, 8vw, 128px);

  /* The accent, under the name the component library asks for.

     tl-components.css is written against --cta / --cta-ink / --cta-text, and
     this surface had only ever defined --green / --green-ink, so every solid
     accent component fell through to its literal fallback and rendered white
     text on a white card. Declared as references rather than copies, so a
     theme that changes --green changes these too. */
  --cta: var(--green);
  --cta-ink: var(--green-ink);
  /* Accent-coloured text on the page ground. The two light blocks below
     darken it to #007842 to clear 4.5:1 on a light page; on the dark theme
     the accent itself is already legible, so it is the accent. */
  --cta-text: var(--green);

  /* Likewise the card surface, which this file calls --card and the
     components ask for as --surface, and the second hairline. */
  --surface: var(--card);
  --line2: var(--line);

  /* The tinted secondary-button ground. This file had never declared it, so
     the button fell through to the library's fallback and mixed its own; the
     contact sheet caught the resulting colour shift on the group and Useful
     Store pages. Declared here, it follows the theme's green. */
  --accent-soft: color-mix(in srgb, var(--cta) 10%, transparent);
}

@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    --ink:24,24,27; --bg-rgb:243,250,254;
    --bg:#F3FAFE; --bg2:#FFFFFF; --card:#FFFFFF; --text:#18181B;
    --prose:#2E2E36; --lede-c:#43434C;
    --muted:#61616A; --faint:#5A5A64;
    --red:#C80018; --orange:#FF4500; --amber:#E07800; --greent:#00A86B;
    --blue:#0066B2; --cyan:#0092C8;
    --green:#00884B; --green-ink:#FFFFFF; --cta-text:#007842; --invalid:#E4553F;
    /* Display tones: for large headings only, where WCAG asks 3:1 rather than
       4.5:1. The previous values were tuned to 4.5 and applied to everything,
       which pushed orange to brown and yellow to olive.

       Computed against the worst of every light ground in use rather than
       against white, because the group's page is #F3FAFE and values that
       cleared 3:1 on white measured 2.85:1 there. */
    --logo-filter: grayscale(1);
    --header-bg: rgba(243,250,254,0.88);
    color-scheme: light;
  }
}
:root[data-theme="light"] {
  --ink:24,24,27; --bg-rgb:243,250,254;
  --bg:#F3FAFE; --bg2:#FFFFFF; --card:#FFFFFF; --text:#18181B;
  --prose:#2E2E36; --lede-c:#43434C;
  --muted:#61616A; --faint:#5A5A64;
  --red:#C80018; --orange:#FF4500; --amber:#E07800; --greent:#00A86B;
  --blue:#0066B2; --cyan:#0092C8;
  --green:#00884B; --green-ink:#FFFFFF; --cta-text:#007842; --invalid:#E4553F;
    /* Display tones: for large headings only, where WCAG asks 3:1 rather than
     4.5:1. The previous values were tuned to 4.5 and applied to everything,
     which pushed orange to brown and yellow to olive. */
  --logo-filter: grayscale(1);
  --header-bg: rgba(243,250,254,0.88);
  color-scheme: light;
}
html { color-scheme: dark; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

*, *::before, *::after { box-sizing: border-box; }
input, textarea, select { min-width: 0; max-width: 100%; }

body {
  margin: 0; background: var(--bg); color: var(--text);
  font-family: 'Inter', system-ui, -apple-system, sans-serif;
  font-size: 16px; line-height: 1.6; overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
  transition: background-color .25s ease, color .25s ease;
}
img { max-width: 100%; height: auto; }

/* The depth line: how far you have gone, in the brand spectrum. Copied from
   BrandOS, where it is driven by the scroll rather than sitting static, so it
   doubles as a reading position. It scales from the left, so with scrolling
   at the top it is a hairline and at the bottom it spans the window. */
body::before {
  content: ''; position: fixed; top: 0; left: 0; height: 3px; width: 100%;
  transform-origin: 0 50%; z-index: 200; pointer-events: none;
  background: var(--tl-rainbow);
  box-shadow: 0 1px 10px rgba(0,163,224,.45);
}
@supports (animation-timeline: scroll(root block)) {
  @media (prefers-reduced-motion: no-preference) {
    body::before { animation: tl-depth linear both; animation-timeline: scroll(root block); }
    @keyframes tl-depth { from { transform: scaleX(0); } to { transform: scaleX(1); } }
  }
}

/* The mark settles once, like something coming to rest in water. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .brand-mark { animation: tl-settle 1.1s cubic-bezier(.22,.9,.24,1) both; }
    @keyframes tl-settle { from { transform: translateY(-5px) scale(.94); opacity: 0; }
                           60%  { transform: translateY(1px) scale(1.01); opacity: 1; }
                           to   { transform: none; opacity: 1; } }
  }
}

/* ---- accent bars -------------------------------------------------------
   One shared rule for the coloured edge on a container. It fades to nothing
   across the width rather than sitting as a solid block of colour: with a
   dozen cards on screen, a dozen solid bars is a lot of colour competing for
   the same attention, and the point of the accent is to identify the card,
   not to be the loudest thing on it. Applied through a class so the number
   of containers that carry one can be limited in one place. */

.accent-bar { position: relative; }
.accent-bar::before {
  content: ''; position: absolute; inset: 0 0 auto 0; height: 2px; border-radius: 2px 2px 0 0;
  background: linear-gradient(90deg, var(--bar, var(--greent)), transparent 78%);
  opacity: .85; pointer-events: none;
}

/* ---- the water --------------------------------------------------------
   Three fixed layers behind everything, copied from BrandOS. None of them
   can affect layout or intercept a click.

   Deliberately CSS only. A canvas particle loop would look wonderful and
   cost battery on every view forever, which is a strange thing to ship
   under a carbon badge. */

@media (prefers-reduced-motion: no-preference) {
  .tl-caustic, .tl-deep, .tl-plight {
    position: fixed; inset: 0; pointer-events: none; z-index: 3; contain: strict;
  }

  /* Sunlight through moving water. Two tinted layers drift against each
     other on different periods, so the pattern never visibly repeats. */
  /* The gradients are drawn soft rather than blurred, and the drift moves
     only the transform.

     BrandOS animates background-position here as well as the transform, and
     wears a 26px blur on top. Both force the compositor to repaint a
     full-viewport blended layer on every frame: measured on this markup it
     halved the frame rate while scrolling, 28.6ms per frame against 16.7ms
     with the layer off. Moving a transform is free by comparison, and the
     result is the same light, so this is the one place worth departing from
     the original. */
  /* Still, not drifting.
     
     BrandOS animates this layer, and moving anything on a full-viewport
     mix-blend-mode surface makes the compositor re-blend the whole screen
     every frame. Measured on this page: 126ms per frame with the drift and
     35ms with the same layer held still, on the same machine in the same
     run. That is 8fps against 28, and it is what the reported lag was.
     
     Removing the blur and animating only a transform was not enough, because
     the blending is the expensive part and the animation is what forces it to
     be redone. The light is identical when it is still, so it is still. The
     page keeps its motion where motion costs nothing: the pointer glow moves
     only while the pointer does, and the depth tint is a scroll-linked
     opacity rather than a moving surface. */
  .tl-caustic {
    mix-blend-mode: screen; opacity: .42;
    background:
      radial-gradient(58% 34% at 20% 16%, rgba(0,172,231,.30), transparent 72%),
      radial-gradient(50% 30% at 76% 30%, rgba(0,157,91,.25), transparent 74%),
      radial-gradient(44% 28% at 44% 72%, rgba(0,111,177,.27), transparent 76%);
  }

  /* Descent. Cross-fades in over the scroll, so the light cools from surface
     cyan toward deep blue the further down the page you are. It tints the
     light, never darkens the page, so text contrast is untouched. */
  .tl-deep {
    mix-blend-mode: screen; opacity: 0; will-change: opacity;
    background:
      radial-gradient(60% 36% at 30% 26%, rgba(0,64,140,.36), transparent 74%),
      radial-gradient(52% 32% at 72% 62%, rgba(12,32,92,.32), transparent 76%);
    animation: tl-descend linear both;
    animation-timeline: scroll(root block);
  }
  @keyframes tl-descend { from { opacity: 0; } to { opacity: .55; } }

  /* A torch underwater. Follows the pointer, desktop only. Smaller and
     lighter than a spotlight: the page carries plenty of colour already. */
  @media (hover: hover) and (pointer: fine) {
    .tl-plight {
      mix-blend-mode: screen; opacity: .17;
      background: radial-gradient(130px 130px at var(--tl-mx, -999px) var(--tl-my, -999px),
                  rgba(var(--tl-glow, 0,163,224), .40),
                  rgba(var(--tl-glow, 0,163,224), .14) 45%, transparent 72%);
      transition: opacity .5s ease;
    }
  }
}

/* The light theme is a bright ground, so the same layers have to sit back. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) .tl-caustic { opacity: .3; }
  :root:not([data-theme="dark"]) .tl-deep { display: none; }
  :root:not([data-theme="dark"]) .tl-plight { opacity: .12; }
}
:root[data-theme="light"] .tl-caustic { opacity: .3; }
:root[data-theme="light"] .tl-deep { display: none; }
:root[data-theme="light"] .tl-plight { opacity: .12; }

/* ---- measure and rhythm ------------------------------------------------
   BrandOS spaces sections with bottom padding only, at 96px, inside a
   1240px measure. Copied rather than re-invented so the three sites scroll
   at the same pace. */

.wrap { width: 100%; max-width: var(--maxw); margin: 0 auto; padding: 0 24px; }
main { display: block; position: relative; z-index: 4; }
/* Bottom-led, as BrandOS is, but with a little above as well: with no top
   padding a heading starts exactly where the previous section's content
   stopped, and dense sections read as jammed together. */
.band { padding: var(--band-pad, clamp(44px, 7vw, 86px)) 24px; }
/* The band is symmetric now, so the first one after a hero already has its
   full measure above it and needs no special case. Left as a no-op rather
   than deleted would be worse than deleting it, so it is deleted. */
.head { padding: clamp(48px, 7vw, 88px) 24px clamp(16px, 2vw, 26px); }
@media (max-width: 640px) { :root { --gap-section: 60px; } }

/* ---- type ---------------------------------------------------------------- */

h1, h2, h3, h4 { font-family: 'Outfit', sans-serif; letter-spacing: -0.03em; line-height: 1.08; }
h1 { font-weight: 900; font-size: clamp(42px, 7vw, 78px); margin: 0 0 22px; }
h2 { font-weight: 800; font-size: clamp(32px, 4.5vw, 50px); margin: 0 0 18px; }
h3 { font-weight: 700; font-size: clamp(20px, 2.4vw, 24px); margin: 0 0 10px; letter-spacing: -0.02em; }
p  { margin: 0 0 16px; color: var(--prose); max-width: var(--measure); }
.lede { font-size: clamp(17px, 2.1vw, 21px); line-height: 1.55; color: var(--lede-c);
        max-width: var(--measure); }
a { color: inherit; }

.eyebrow { font-size: 11.5px; font-weight: 800; letter-spacing: .12em; text-transform: uppercase;
           color: var(--faint); margin: 0 0 14px; position: relative; padding-left: 26px; }
.eyebrow::before { content: ''; position: absolute; left: 0; top: .55em; width: 16px; height: 2px;
                   background: var(--accent-line, var(--greent)); }

/* ---- heading gradients ----------------------------------------------------
   The hero keeps the full spectrum. Every heading after it takes a two-colour
   gradient that steps one place around the wheel, so a page reads as a
   progression instead of repeating the same rainbow six times.

   The stops are theme tokens rather than fixed hex. The old rule used raw
   values chosen against near-black, which meant the yellow and cyan stretches
   of a heading were close to invisible on the light theme. lib/gradient.js
   assigns the classes; nobody writes them by hand. */

.rainbow { background-image: linear-gradient(90deg, var(--g-from, var(--tone-red)),
                                                    var(--g-to,   var(--tone-orange)));
           -webkit-background-clip: text; background-clip: text; color: transparent; }

/* The hero, and only the hero.

   The rainbow is the group's signature and it only reads as one while it is
   rare. Running it across every section turned six meaningful colours into
   decoration and made the page look busy rather than considered, so the
   spectrum now appears once per page and the sections below it take a single
   colour each. */
.rainbow.spectrum { background-image: linear-gradient(90deg,
    var(--tone-red), var(--tone-orange), var(--tone-amber),
    var(--tone-green), var(--tone-blue), var(--tone-purple)); }

/* Sections: one colour, not a gradient.

   Kept as a background-image of a single stop rather than a plain colour so
   that .rainbow's background-clip still applies and nothing else has to change.
   The cycle walks the palette so consecutive sections differ, which is what the
   gradient sequence was really for. */
.hue-1 { --g-from: var(--tone-red);    --g-to: var(--tone-red); }
.hue-2 { --g-from: var(--tone-orange); --g-to: var(--tone-orange); }
.hue-3 { --g-from: var(--tone-green);  --g-to: var(--tone-green); }
.hue-4 { --g-from: var(--tone-blue);   --g-to: var(--tone-blue); }
.hue-5 { --g-from: var(--tone-cyan);   --g-to: var(--tone-cyan); }
.hue-6 { --g-from: var(--tone-purple); --g-to: var(--tone-purple); }

/* A gradient is decoration. If the browser cannot clip a background to text,
   transparent text would be invisible text, so the colour comes back. */
@supports not ((background-clip: text) or (-webkit-background-clip: text)) {
  .rainbow { background-image: none; color: var(--text); }
}

/* ---- bullets --------------------------------------------------------------
   The house list. A paragraph that could be four bullets should be four
   bullets: the objective is a quick overview, not a reading task. */

.bullets { list-style: none; padding: 0; margin: 20px 0; display: grid; gap: 10px;
           max-width: var(--measure); }
.bullets li { position: relative; padding-left: 26px; color: var(--prose); font-size: 15.5px;
              line-height: 1.5; }
.bullets li::before { content: ''; position: absolute; left: 4px; top: .58em; width: 8px; height: 8px;
                      border-radius: 50%; background: var(--bullet, var(--greent)); }
.bullets strong { color: var(--text); font-weight: 700; }
.bullets.is-two { grid-template-columns: 1fr; max-width: none; }
@media (min-width: 760px) { .bullets.is-two { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px 34px; } }

/* ---- buttons -----------------------------------------------------------------
   Moved to tl-components.css. The family was defined here and again in
   webos.css with different padding and an opposite secondary weight, and the
   sprint tracker loaded neither. One definition now, for all ten surfaces. */

.herobtns { display: flex; flex-wrap: wrap; gap: 12px; margin: 30px 0 0; }

/* ---- client marks -------------------------------------------------------------
   Inverted in the dark theme, which is what removes the white rectangle a
   black-on-white logo otherwise arrives inside. */

.marks-band { padding: 30px 0; border-block: 1px solid var(--line2); overflow: hidden; }
.marks-track { overflow: hidden; }
.marks { list-style: none; margin: 0; padding: 0; display: flex; align-items: center;
         gap: 40px; width: max-content; animation: tlmarquee 80s linear infinite; }
@keyframes tlmarquee { from { transform: translateX(0); } to { transform: translateX(-50%); } }
.marks li { display: flex; align-items: center; flex-shrink: 0; }
.marks img { height: 56px; width: auto; filter: var(--logo-filter); opacity: .72; }
.marks img.is-half { height: 28px; }

/* Motion is the whole point of a marquee, so with it switched off the strip
   becomes what it would otherwise be: a wrapped, centred list. */
@media (prefers-reduced-motion: reduce) {
  .marks { animation: none; width: auto; flex-wrap: wrap; justify-content: center; gap: 18px 40px; }
  .marks li[aria-hidden="true"] { display: none; }
}
@media (max-width: 700px) {
  .marks { gap: 26px; }
  .marks img { height: 38px; }
  .marks img.is-half { height: 22px; }
}

/* On /about the same marks are a grid to be read, not a strip to drift past. */
/* A wall, not a ladder.
   These marks run from 45px to 200px wide at a common height, so a wrapping
   flex row packs each line differently and the rows step down: eleven, then
   eight, then seven, with the short last row hanging. A grid gives every mark
   the same cell and centres it, so the wall reads as a matrix and the last
   row lines up with the ones above it. */
.marks.is-grid { animation: none; width: auto; margin-top: 24px;
                 display: grid; gap: 26px 18px;
                 grid-template-columns: repeat(auto-fill, minmax(148px, 1fr));
                 align-items: center; justify-items: center; }
.marks.is-grid li { justify-content: center; width: 100%; }
.marks.is-grid img { height: auto; max-height: 46px; max-width: 100%; object-fit: contain; }
.marks.is-grid li[aria-hidden="true"] { display: none; }
@media (max-width: 640px) {
  .marks.is-grid { grid-template-columns: repeat(auto-fill, minmax(104px, 1fr)); gap: 20px 14px; }
  .marks.is-grid img { max-height: 36px; }
}

/* ---- header ---------------------------------------------------------------------- */

header { position: sticky; top: 0; z-index: 50; background: var(--header-bg);
         backdrop-filter: blur(16px); border-bottom: 1px solid var(--line); }
.bar { display: flex; align-items: center; justify-content: space-between; gap: 16px;
       padding: 14px 24px; position: relative; }
.brand { display: flex; align-items: center; gap: 11px; text-decoration: none; color: var(--text);
         font-family: 'Outfit', sans-serif; font-weight: 800; font-size: 22px;
         letter-spacing: -0.03em; white-space: nowrap; min-width: 0; }
.brand-mark { display: inline-block; flex-shrink: 0; width: 36px; height: 36px;
              background-image: url('/assets/logo.webp'); background-size: auto 100%;
              background-position: left center; background-repeat: no-repeat; }
.brand-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; }

header nav { display: flex; align-items: center; gap: 26px; }
header nav a { color: var(--muted); text-decoration: none; font-size: 14.5px; font-weight: 500;
               transition: color .25s ease; }
header nav a:hover, header nav a.on { color: var(--text); }
header nav a.cta { background: var(--cta, var(--green)); color: var(--cta-ink, var(--green-ink));
                   font-weight: 700; padding: 11px 20px; border-radius: 999px; font-size: 14px; }
header nav a.cta:hover { filter: brightness(1.06); color: var(--cta-ink, var(--green-ink)); }

.burger { display: none; background: none; border: 0; cursor: pointer; padding: 8px;
          flex-direction: column; gap: 5px; }
.burger span { display: block; width: 22px; height: 2px; background: var(--text); border-radius: 2px; }
.burger[aria-expanded="true"] span:nth-child(2) { opacity: 0; }

.themetoggle { position: relative; display: inline-grid; place-items: center; width: 38px; height: 38px;
               border-radius: 999px; border: 1px solid var(--line); background: var(--bg2);
               color: var(--muted); cursor: pointer; padding: 0; flex-shrink: 0;
               transition: color .25s ease, border-color .25s ease; }
.themetoggle:hover { color: var(--text); border-color: var(--text); }
.themetoggle svg { position: absolute; width: 17px; height: 17px;
                   transition: opacity .25s ease, transform .25s ease; }
.t-sun { opacity: 0; transform: rotate(-90deg) scale(.6); }
.t-moon { opacity: 1; }
:root[data-theme="light"] .t-sun { opacity: 1; transform: none; }
:root[data-theme="light"] .t-moon { opacity: 0; transform: rotate(90deg) scale(.6); }
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) .t-sun { opacity: 1; transform: none; }
  :root:not([data-theme="dark"]) .t-moon { opacity: 0; transform: rotate(90deg) scale(.6); }
}
@media (prefers-reduced-motion: reduce) { .themetoggle svg { transition: none; transform: none; } }

@media (max-width: 900px) {
  .burger { display: flex; }
  .brand { font-size: 19px; gap: 9px; }
  .brand-mark { width: 32px; height: 32px; }
  header nav { position: absolute; top: 100%; left: 0; right: 0; flex-direction: column;
               align-items: stretch; gap: 0; background: var(--bg);
               border-bottom: 1px solid var(--line); padding: 8px 0 18px; display: none; }
  header nav.open { display: flex; }
  header nav a { padding: 14px 24px; font-size: 16px; border-top: 1px solid var(--line2); }
  header nav a.cta { margin: 14px 24px 0; text-align: center; border-top: 0; }
  .themetoggle { position: absolute; top: 12px; right: 64px; }
}
@media (max-width: 430px) {
  .brand-name { position: absolute; width: 1px; height: 1px; overflow: hidden;
                clip-path: inset(50%); white-space: nowrap; }
}

/* ---- forms, the old way --------------------------------------------------
   Superseded by tl-components.css. Scoped to forms that have not been moved
   across yet, so the two can coexist during the migration: without the
   :not(), `form:not(.tl-form) label { margin-bottom: 18px }` puts eighteen pixels between
   every new label and its own input.

   This block shrinks as forms move over, and goes when the last one has.
   ------------------------------------------------------------------------ */
/* Only a label that has not been given a class.
 *
 * This rule is (0,1,2) and a component's own class is (0,1,0), so it beat
 * every labelled control in an unmigrated form and reimposed display:block
 * on it. The booking calendar's day cells are labels, so each one was
 * blocked rather than centred and every date sat against the top of its box:
 * reported 24 September as the booking section "not loading properly", which
 * is what that looks like. The .field and .agree labels were surviving only
 * because they set display themselves further down the cascade.
 *
 * Same principle as the submit button above: a rule that dresses an
 * undressed control stands aside for one that is already dressed. */
form:not(.tl-form) label:not([class]) { display: block; margin-bottom: 18px; font-size: 13.5px; font-weight: 600; color: var(--text); }
form:not(.tl-form) .opt { font-weight: 400; color: var(--faint); }
form:not(.tl-form) .row { display: grid; gap: 18px; grid-template-columns: 1fr; }
@media (min-width: 620px) { form:not(.tl-form) .row { grid-template-columns: 1fr 1fr; } }
form:not(.tl-form) input[type=text], form:not(.tl-form) input[type=email], form:not(.tl-form) input:not([type]), form:not(.tl-form) textarea, form:not(.tl-form) select {
  display: block; width: 100%; margin-top: 8px; padding: 14px 16px; font: inherit; font-weight: 400;
  color: var(--text); background: var(--bg); border: 1px solid var(--line); border-radius: 14px; }
form:not(.tl-form) select { min-height: 54px; padding-right: 44px; appearance: none; -webkit-appearance: none;
  cursor: pointer;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: calc(100% - 22px) 55%, calc(100% - 16px) 55%;
  background-size: 6px 6px, 6px 6px; background-repeat: no-repeat; }
form:not(.tl-form) select option { background: var(--bg2); color: var(--text); }
form:not(.tl-form) :is(input, textarea, select):focus-visible { outline: 2px solid var(--cta, var(--greent));
  outline-offset: 2px; border-color: var(--cta, var(--greent)); }
form:not(.tl-form) :is(input, textarea, select).bad { border-color: #FF8A8A; }
form:not(.tl-form) textarea { resize: vertical; }
form:not(.tl-form) .check { display: flex; gap: 11px; align-items: flex-start; font-weight: 400;
              font-size: 13.5px; color: var(--muted); margin: 22px 0; }
form:not(.tl-form) .check input { margin-top: 3px; flex-shrink: 0; }
/* Only a button that has not been given a class. This rule is (0,2,1) and
   .btn.ghost is (0,2,0), so a <button class="btn ghost small"> in a plain
   form was painted as a solid primary submit whatever it asked for: the
   guided chooser's back control came out as a full-width green slab on the
   Studio. A rule that dresses an undressed control should stand aside for
   one that is already dressed.

   .cur-opt joined the list on 29 September for the same reason and with a
   worse symptom. The currency toggle is two submit buttons in a plain form,
   so both were painted the solid primary green, and the `.on` class that
   marks the chosen one is (0,2,0) against this rule's (0,5,2). Both sides
   of a two-way toggle therefore looked selected, which is not a styling
   slip: a control whose whole job is to show which of two things is current
   showed both. */
form:not(.tl-form) button[type=submit]:not(.btn):not(.tl-btn):not(.linkish):not(.cur-opt) {
  font: inherit; font-weight: 700; font-size: 15px; cursor: pointer;
  padding: 14px 30px; border-radius: 999px; border: 1px solid var(--cta, var(--green));
  background: var(--cta, var(--green)); color: var(--cta-ink, var(--green-ink)); }
form:not(.tl-form) button[type=submit]:not(.btn):not(.tl-btn):not(.linkish):not(.cur-opt):hover { filter: brightness(1.08); }
form:not(.tl-form) button[disabled] { opacity: .55; cursor: not-allowed; }
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; opacity: 0; }
.formnote { margin: 14px 0 0; font-size: 14.5px; min-height: 1.2em; }
.formnote.ok { color: var(--greent); }
.formnote.err { color: #FF8A8A; }

/* ---- footer ---------------------------------------------------------------------------- */

/* No margin above it. The last band already ends on a full --gap-section,
   the footer then added 40px of margin and up to 68px of its own padding, and
   the three compounded into roughly 200px of dark nothing between the last
   thing on the page and the first thing in the footer. The rainbow rule at
   the top of the footer is the separator; it does not need a gap as well. */
footer { position: relative; z-index: 4; margin-top: 0;
         padding: clamp(34px, 3.4vw, 52px) 0 34px; }
/* The last band stops short, since the footer supplies the space below it. */
main > .band:last-child { padding-bottom: clamp(24px, 3vw, 48px); }
footer::before { content: ''; position: absolute; inset: 0 0 auto 0; height: 2px; opacity: .55;
  background: var(--tl-rainbow); }
footer .cols { display: grid; gap: 30px;
               grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr));
               padding-top: 32px; border-top: 1px solid var(--line2); }
footer h2 { font-size: 12px; letter-spacing: .1em; text-transform: uppercase; color: var(--faint);
            margin: 0 0 14px; font-family: 'Inter', sans-serif; font-weight: 700; }
footer ul { list-style: none; padding: 0; margin: 0; }
footer li { margin-bottom: 10px; }
footer a { color: var(--muted); text-decoration: none; font-size: 14.5px; }
footer a:hover { color: var(--text); }
.foot-note { color: var(--faint); font-size: 14.5px; margin-top: 16px; }
footer .fine { display: flex; gap: 16px; flex-wrap: wrap; justify-content: space-between;
               margin-top: 32px; padding-top: 24px; border-top: 1px solid var(--line2);
               color: var(--faint); font-size: 14.5px; }
.carbon { display: inline-flex; align-items: center; gap: 8px; }
.carbon i { width: 7px; height: 7px; border-radius: 50%; background: var(--greent); flex-shrink: 0; }

.foot-call { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: 24px 34px; padding: 44px 28px; margin-bottom: 36px; border-radius: 28px;
  border: 1px solid var(--line2); background: var(--bg2); }
.foot-call > div { flex: 1 1 420px; }
.foot-call strong { display: block; font-family: 'Outfit', sans-serif; font-weight: 800;
  font-size: clamp(26px, 3.6vw, 38px); line-height: 1.12; letter-spacing: -0.03em; margin: 10px 0 12px; }
.foot-call p { color: var(--muted); font-size: 15px; margin: 0; max-width: 52ch; }

/* ---- reveal -------------------------------------------------------------------------------- */

/* Copied from BrandOS, including the three things mine had wrong, and then
   a fourth that was mine alone.

   It starts at 0.15 rather than 0, so a section is always faintly there and
   never reads as a blank page. It sits inside an @supports guard, so a
   browser without scroll-driven animations gets no animation at all instead
   of one that runs on the document timeline.

   THE FILL MODE IS THE WHOLE BUG. This said "animation: tlrise linear both".
   'both' holds the first keyframe before the animation starts, and a view()
   timeline that does not advance never starts it, so every .band sat at
   opacity .15 permanently: readable enough to look like a styling choice,
   faint enough to look broken, and fully clickable throughout. Reported on 24
   September as "works look faded, 50% times I have scrolled past it, it looks
   inactive however the click and interactions work properly". Measured at
   0.15 at eight scroll positions across a 1310px section, never once
   resolving.

   Without a fill mode an element outside the animation's range renders
   normally, which is the correct failure: where the timeline works the
   section rises, and where it does not the section is simply there. A reveal
   must never be able to hide content, which is the same rule as a number
   never being printed unless it is true.

   The range is a length rather than a percentage for a related reason.
   'entry 30%' is 30% of the element's own height plus the viewport, so a tall
   section resolved hundreds of pixels later than a short one; 320px is the
   same distance for every section on the page. */

@keyframes tlrise { from { opacity: .15; transform: translateY(26px); } to { opacity: 1; transform: none; } }

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .band, .head, .hero {
      animation: tlrise linear;
      animation-timeline: view();
      animation-range: entry 0% entry 320px;
    }
    /* The first screen is already in view on load, so it must not sit at
       0.15 waiting for a scroll that may never come. */
    .hero { animation-range: cover 0% cover 1%; }
  }
}

/* ---- icons -------------------------------------------------------------
   A drawn mark on a plate tinted with its own colour. Minimal on purpose:
   the tint is 14%, the border 28%, so a grid of four reads as one family
   rather than four competing swatches. */

.ico { display: grid; place-items: center; width: 44px; height: 44px; border-radius: 13px;
       flex-shrink: 0; color: var(--ic, var(--greent));
       background: color-mix(in srgb, var(--ic, var(--greent)) 14%, transparent);
       border: 1px solid color-mix(in srgb, var(--ic, var(--greent)) 28%, transparent);
       transition: background .3s ease, border-color .3s ease; }
.ico svg { width: 21px; height: 21px; display: block; }
@supports not (color: color-mix(in srgb, red, blue)) {
  .ico { background: rgba(var(--ink),.05); border-color: var(--line); }
}
@media (hover: hover) {
  .svc:hover .ico, .process li:hover .ico {
    background: color-mix(in srgb, var(--ic, var(--greent)) 22%, transparent);
    border-color: color-mix(in srgb, var(--ic, var(--greent)) 46%, transparent);
  }
}
@media (max-width: 640px) { .ico { width: 40px; height: 40px; border-radius: 12px; }
                            .ico svg { width: 19px; height: 19px; } }

/* A deliberate line break in a heading is for the wide view. Below that the
   heading wraps on its own and the break only makes a short orphan line. */
@media (max-width: 560px) { h1 br, h2 br { display: none; } }

/* A line break that is a hint rather than a rule. A <br> inside a heading
   breaks at every width, including the ones where the line has already
   wrapped, and the words either side of it then run together because a <br>
   is not a space. This is a space that becomes a break when there is room. */
.brk { display: inline; }
@media (min-width: 700px) { .brk { display: block; height: 0; } }
