/*
  DataraFlow design layer — token system + utilities for the marketing site
  (and, later, the admin and storefront restyle). Loaded as a sibling of
  app.css, never in place of it. Bootstrap 5 is still on the page in app
  contexts; nothing in here reaches for `!important` or a Bootstrap class
  name, so the two coexist rather than fight.

  Organization: tokens -> base element defaults -> layout utilities ->
  components -> motion utilities -> reduced-motion override.
*/

/* ---------------------------------------------------------------------- */
/* Tokens                                                                  */
/* ---------------------------------------------------------------------- */

:root {
  /* Ink & surface — warm paper, not clinical white-on-gray. */
  --ink-900: #0B1220;
  --ink-700: #16233A;
  --ink-500: #33415C;
  --paper: #FDFBF7;
  --surface: #FFFFFF;
  --surface-sunken: #F4F1EA;
  --line: #E5E0D6;

  /* One hot accent, used sparingly, plus status colors. --accent is the
     brand color: focus rings, the eyebrow label, decorative SVG strokes,
     underline-slide accents. It is NEVER a fill under white text — white
     on #F4590D is 3.34:1, failing WCAG AA (4.5:1 required at 16px/600,
     which doesn't clear the large-text 3:1 floor). Any solid accent fill
     meant to hold white text uses --accent-deep instead (5.01:1). */
  --accent: #F4590D;
  --accent-deep: #C93F00;
  --go: #0E8345;
  --stop: #B42318;

  /* Derived shades. Recalibration within the brand, not new brand colors:
     every one is computed from a token above, and the derivation is written
     down so the next edit can recompute it instead of eyeballing it.

     --ink-800 = mix(--ink-900, --ink-500, 18%). Second stop of the vertical
       wash on the two ink bands, so a band has depth rather than reading as
       one flat rectangle.
     --ink-300 = mix(--ink-500, --paper, 33%). The quiet display numeral on
       the how-it-works ledger, and nothing else: it is safe as WCAG LARGE
       TEXT only (see the ledger below), so it is not a body ink and must
       never be used as one.
     --rule = --ink-900 at 7%, --rule-inverse = #FFFFFF at 10%. Hairlines of
       the ledger motif, on light surfaces and on the ink bands. Decorative
       rules only, never text, and pinned that way.
     --accent-wash = --accent at 8%. A tint under body ink, never a text
       color, and never under small accent text (see REFUSED below).

     CONTRAST LEDGER. Every line is RECOMPUTED FROM THE HEXES IN THIS FILE by
     tests/test_design_layer.py, which fails if the number here and the number
     the color actually produces disagree, or if a permitted pair drops under
     its floor. Claims here cannot drift from the colors: re-tune a token and
     the test moves the number for you. Syntax: `fg on bg = ratio floor min`,
     `@NN%` is alpha over the background, `a/b` is a composited over b.
     Floors are WCAG AA: 4.5:1 normal text, 3:1 large text (>=24px, or
     >=18.66px bold).

     PERMITTED
       #FFFFFF on --ink-900 = 18.72:1 floor 3:1        h1/h2, 48px/700
       #FFFFFF@78% on --ink-900 = 11.52:1 floor 4.5:1  .section-ink p, li
       #FFFFFF@62% on --ink-900 = 7.58:1 floor 4.5:1   .section-ink .cta-note
       --accent on --ink-900 = 5.60:1 floor 4.5:1      .section-ink .eyebrow
       #FFFFFF on --ink-800 = 17.38:1 floor 3:1        h1/h2, 48px/700
       #FFFFFF@78% on --ink-800 = 10.91:1 floor 4.5:1  .section-ink p, li
       #FFFFFF@62% on --ink-800 = 7.29:1 floor 4.5:1   .section-ink .cta-note
       --accent on --ink-800 = 5.20:1 floor 4.5:1      .section-ink .eyebrow
       --ink-300 on --paper = 3.94:1 floor 3:1         .step-number, 36-48px/700
       --ink-300 on --surface = 4.08:1 floor 3:1       .step-number, 36-48px/700
       --ink-300 on --surface-sunken = 3.61:1 floor 3:1  .step-number
       --ink-900 on --accent-wash/--surface-sunken = 15.20:1 floor 4.5:1  h3
       --ink-700 on --accent-wash/--surface-sunken = 12.76:1 floor 4.5:1  body

     PERMITTED, added with the subpages (features, roadmap, about, contact,
     terms, privacy, offline, and the four auth cards). The landing page uses
     ink bands and one washed bento cell; the subpages are mostly ink on the
     three light surfaces, so the pairs that carry their reading text are
     recomputed here too rather than assumed safe by family resemblance.
       --ink-900 on --paper = 18.12:1 floor 4.5:1      headings, body links
       --ink-900 on --surface = 18.72:1 floor 4.5:1    headings on a card
       --ink-900 on --surface-sunken = 16.60:1 floor 4.5:1  headings, sunken links
       --ink-700 on --paper = 15.21:1 floor 4.5:1      body default
       --ink-700 on --surface = 15.72:1 floor 4.5:1    body on a card or row
       --ink-700 on --surface-sunken = 13.94:1 floor 4.5:1  roadmap entry body
       --ink-500 on --paper = 9.90:1 floor 4.5:1       .cta-note, .legal-updated
       --ink-500 on --surface = 10.24:1 floor 4.5:1    row labels, .df-muted
       --ink-500 on --surface-sunken = 9.08:1 floor 4.5:1  quiet note on sunken
       --accent-deep on --paper = 4.84:1 floor 4.5:1   story and legal links
       --accent-deep on --surface = 5.01:1 floor 4.5:1  links inside a card
       --stop on --surface = 6.57:1 floor 4.5:1        form field errors

     PERMITTED, added with the storefront round. Two hexes rather than two
     tokens, and deliberately so: the WhatsApp CTA on a tenant's item page
     (app.css, `.df-whatsapp-btn`) is the one sanctioned non-token colour on
     this site, because that button opens WhatsApp and has to read as
     WhatsApp rather than as this product's accent. It therefore has no
     token family to be derived from, and its arithmetic used to live only
     in a prose comment beside the rule. The ledger's grammar resolves a
     literal exactly as it resolves a token, so both fills are carried here
     instead: the numbers below are recomputed from the hexes the button
     actually paints, and re-tuning either fill moves the number or fails.
       #FFFFFF on #0E7A6B = 5.23:1 floor 4.5:1         .df-whatsapp-btn
       #FFFFFF on #0C685B = 6.67:1 floor 4.5:1         .df-whatsapp-btn:hover

     PERMITTED AS NON-TEXT, added with the admin round. Floor 3:1, not 4.5:1,
     and the difference is the whole point of the section: WCAG 1.4.11 asks
     3:1 of a UI-component boundary or a focus/state indicator, 1.4.3 asks
     4.5:1 of text. The three below draw a BOUNDARY on the sunken plane and
     never carry a word:
       - the 2px accent underline `.df-navlink:hover` grows under a sidebar
         link, and the same underline on `.df-install-banner__install`;
       - the 1px semantic border on a `.df-status` chip.
     `--accent-deep on --surface-sunken` therefore appears TWICE in this
     ledger, once here at 3:1 and once under REFUSED at 4.5:1. That is not a
     contradiction and not a duplicate to be tidied away: it is the same
     arithmetic answering two different questions, and the REFUSED line is
     what keeps the pair out of text while this line lets it draw the line
     under the text. Delete either and the file starts lying about one of
     the two.
       --accent-deep on --surface-sunken = 4.44:1 floor 3:1  hover underline, chip border
       --go on --surface-sunken = 4.28:1 floor 3:1      .df-status--completed border
       --stop on --surface-sunken = 5.83:1 floor 3:1    .df-status--abandoned/--overdue border

     The focus ring, on the same 3:1 non-text floor and carried here for the
     first time. `a/button:focus-visible` above and app.css's form-control
     rules all draw `outline: 2px solid var(--accent)`, and the ratio that
     indicator needs against what surrounds it had never been written down.
     It clears the floor on both plain surfaces and does NOT clear it on the
     sunken one; that third line is under REFUSED, where it can be argued
     with rather than discovered again.
       --accent on --surface = 3.34:1 floor 3:1         focus ring on a card or input
       --accent on --paper = 3.23:1 floor 3:1           focus ring on the page ground

     PERMITTED, added with the admin round: the three Bootstrap alert
     emphasis inks. `.alert-success`/`-danger`/`-warning` (app.css) paint a
     12-14% tint of a semantic token over --surface and then set the alert's
     text to that same token. Every one of those pairs lands in the 4.1-4.4
     band — a colour is never far enough from a tint of itself — so success
     was 4.11:1 and warning 4.24:1, both AA failures, and danger passed at
     5.40:1 only by being the darkest of the three. The fix is one rule for
     all three rather than a patch for the two that failed: the emphasis ink
     is the token darkened 85% toward --ink-900, the same `color-mix` step
     `.btn-primary`'s hover already uses. `a@NN%/b` is exactly
     `color-mix(in srgb, a NN%, b)` when b is opaque, so the ledger's
     existing grammar states both the ink and the tint it sits on without
     needing to learn a new function.
       --accent-deep@85%/--ink-900 on --accent@14%/--surface = 5.35:1 floor 4.5:1  .alert-warning
       --go@85%/--ink-900 on --go@12%/--surface = 5.11:1 floor 4.5:1               .alert-success
       --stop@85%/--ink-900 on --stop@12%/--surface = 6.59:1 floor 4.5:1           .alert-danger

     PERMITTED, the same warning-emphasis ink on the two plain surfaces it
     also lands on. `--bs-warning-text-emphasis` is declared on `:root` in
     app.css rather than only inside `.alert-warning`, because Bootstrap's
     `.text-warning-emphasis` utility reads it too. That utility replaced two
     uses of `.text-warning`, which is the raw `--bs-warning-rgb` amber
     (#FFC107) and is 1.63:1 on --surface — not a near miss but unreadable,
     and it was carrying the line that tells an owner their shop is not
     published. The badge variant `.text-bg-warning` keeps Bootstrap's amber
     fill under black text and is untouched, which is why `--bs-warning-rgb`
     itself is still not redeclared.
       --accent-deep@85%/--ink-900 on --surface = 6.32:1 floor 4.5:1  .text-warning-emphasis
       --accent-deep@85%/--ink-900 on --paper = 6.12:1 floor 4.5:1    .text-warning-emphasis

     REFUSED (documented failures, the reason a rule exists)
       --accent-deep on --accent-wash/--surface-sunken = 4.06:1 under 4.5:1
         This is why no .eyebrow may sit on a washed surface. If a future
         palette change makes this pass, the ban can be lifted; the test
         says so rather than staying quietly stale.
       --accent-deep on --surface-sunken = 4.44:1 under 4.5:1
         Close enough to look fine and it is not: this is a real AA failure
         the subpage round found shipped, on the contact confirmation panel
         (`.contact-done a`, an --accent-deep link inside a sunken box) and
         nowhere else. The fix is the rule that a link on a sunken surface
         takes the site's default anchor treatment instead: --ink-900 ink
         (16.60:1 above) with the accent underline slide, which keeps the
         accent visible as the affordance without asking it to carry text.
         "and nowhere else" was true of the MARKETING side and was read as
         though it were true of the product. The admin round found two more
         live instances, both of them written as `var(--df-accent)` — the
         app.css alias for this very token — which is why the guard that
         was supposed to catch them did not: `.df-navlink:hover`, on the
         sidebar of every admin page, and `.df-install-banner__install`,
         the install affordance in the app shell. Both now take the ruling
         above; the underline that carries the accent for them is ledgered
         as non-text at 3:1 in the section further up.
       --accent on --surface-sunken = 2.96:1 under 3:1
         THE ADMIN ROUND'S CARRIED FINDING, recorded here rather than in a
         report nobody will re-read. Every focus ring on this site is
         `outline: 2px solid var(--accent)`, and WCAG 2.2 SC 1.4.11 asks 3:1
         of a focus indicator against what is adjacent to it. On --surface
         and --paper it clears (3.34 and 3.23, above). On the sunken plane it
         does not, so a keyboard user tabbing across a hovered table row, the
         sidebar's hover state, the storefront's header band or its footer
         gets an indicator 0.04 under the floor.
         NOT FIXED THIS ROUND, and the reason is a scope judgment rather than
         an oversight. The one-line fix is `--accent-deep` (4.44:1 on the
         same plane), but the ring is the TOKEN layer's, shared by the
         marketing pages, the storefront and the admin; app.css draws its own
         copy for form controls as a border plus a 30% glow, and changing
         only one of the two forks the product into two focus treatments,
         which is worse than one ring with a known 0.04 margin. It is a
         four-surface change and belongs to a round that can look at all
         four. Until then this line is what stops it being rediscovered.
       --ink-300 on --paper = 3.94:1 under 4.5:1
       --ink-300 on --surface = 4.08:1 under 4.5:1
       --ink-300 on --surface-sunken = 3.61:1 under 4.5:1
         The three above are why --ink-300 is large-text only. */
  --ink-800: #121A2B;
  --ink-300: #767E8F;
  --rule: rgba(11, 18, 32, 0.07);
  --rule-inverse: rgba(255, 255, 255, 0.10);
  --accent-wash: rgba(244, 89, 13, 0.08);

  /* Type */
  --font-display: "Sora", system-ui, sans-serif;
  --font-ui: "Inter", system-ui, sans-serif;

  /* Spacing — 8pt scale. */
  --s1: 8px;
  --s2: 16px;
  --s3: 24px;
  --s4: 32px;
  --s5: 40px;
  --s6: 48px;
  --s7: 56px;
  --s8: 64px;
  /* The upper half of the same 8pt scale. Section rhythm needs steps above
     64px to be varied at all: with only --s8 available every section was
     padded identically, which is what made the landing page read as one
     undifferentiated column. */
  --s9: 80px;
  --s10: 96px;
  --s11: 128px;

  /* Radii */
  --r-sm: 8px;
  --r-lg: 16px;
  --r-xl: 24px;

  /* Shadows — soft, warm-tinted depth rather than a flat gray blur. A
     low ambient layer plus a tighter contact layer, both tinted off the
     ink color at low opacity so they read as light falling on paper. */
  --shadow-1: 0 1px 2px rgba(11, 18, 32, 0.06), 0 4px 10px rgba(11, 18, 32, 0.06);
  --shadow-2: 0 2px 4px rgba(11, 18, 32, 0.08), 0 16px 32px rgba(11, 18, 32, 0.14);

  /* Motion */
  --ease: cubic-bezier(.2, .8, .2, 1);
  --fast: 200ms;
}

/* ---------------------------------------------------------------------- */
/* Base element defaults                                                   */
/* ---------------------------------------------------------------------- */

/* The one reset in this file, and it is load-bearing rather than tidy.

   The marketing pages, the offline page and the auth pages all load this
   stylesheet; only the auth pages also load Bootstrap, which is where
   `box-sizing: border-box` was quietly coming from. Everywhere else the
   default `content-box` was in force, so any element with both an explicit
   width and padding measured wider than it asked for: the contact form's
   inputs (`width: 100%` plus `--s2` each side, `1px` border each side)
   overflowed their card's padding by 34px — 2 × 16px padding plus 2 × 1px
   border, not the 32px padding alone — and the features page's overlapping
   copy panel pushed 50px of horizontal scroll onto a 390px phone. Both were
   invisible on desktop and both were real.

   Declared here rather than per page because it is the assumption every
   width in every marketing template was already written under. */
*,
*::before,
*::after {
  box-sizing: border-box;
}

body {
  background: var(--paper);
  color: var(--ink-700);
  font-family: var(--font-ui);
}

h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-display);
  color: var(--ink-900);
  line-height: 1.15;
  letter-spacing: -0.01em;
}

a {
  color: var(--ink-900);
  text-decoration: none;
  background-image: linear-gradient(var(--accent), var(--accent));
  background-repeat: no-repeat;
  background-position: 0 100%;
  background-size: 0% 2px;
  transition: background-size var(--fast) var(--ease);
}

a:hover {
  background-size: 100% 2px;
}

a:focus-visible,
button:focus-visible,
.btn-accent:focus-visible,
.btn-quiet:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ---------------------------------------------------------------------- */
/* Layout utilities                                                        */
/* ---------------------------------------------------------------------- */

.container-marketing {
  max-width: 1140px;
  margin-left: auto;
  margin-right: auto;
  padding-left: var(--s3);
  padding-right: var(--s3);
}

.section {
  padding-top: var(--s8);
  padding-bottom: var(--s8);
}

@media (max-width: 767px) {
  .section {
    padding-top: var(--s6);
    padding-bottom: var(--s6);
  }
}

.eyebrow {
  display: inline-block;
  font-family: var(--font-ui);
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent-deep);
}

/* ---------------------------------------------------------------------- */
/* The ledger mark                                                         */
/* ---------------------------------------------------------------------- */

/* The motif the landing page introduced and every subpage now inherits: a
   hairline opening a block with a short accent run sitting on its left end.
   One entry in a book of accounts, and the same gesture as the accent
   stretch that closes the hero band.

   Shared here rather than restated per page, because seven templates draw
   it now. It is a border, not decoration: every use separates real entries
   from each other, which is the only reason a line is allowed to exist.

   `.ledger-rule--label` is the same rule carrying a running head in its
   left end instead of the bare accent run — a ledger column header, set in
   the display face at reading size and sentence case. Deliberately not an
   `.eyebrow`: an uppercase tracked micro-label above every one of six
   feature rows is the templated rhythm this redesign exists to avoid, and
   the label here is doing structural work (naming a column of entries) that
   an eyebrow does not do. */
.ledger-rule {
  position: relative;
  border-top: 1px solid var(--line);
}

.ledger-rule::before {
  content: "";
  position: absolute;
  top: -1px;
  left: 0;
  width: 28px;
  height: 1px;
  background: var(--accent);
}

.ledger-rule--label {
  display: flex;
  align-items: baseline;
  gap: var(--s2);
  padding-top: var(--s2);
}

.ledger-rule--label > .ledger-label {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.95rem;
  color: var(--ink-500);
}

/* The same mark at band scale: the hairline that closes an ink band, with
   the accent holding a stretch of it. `--accent-run` is how far along the
   band the accent reaches, so a page sets the length rather than restating
   the gradient; `--band-rule-reversed` swaps which end the accent owns.
   The landing opens its hero at 24% and closes the page at 62% from the
   other end — one partner's stretch of the ledger, then the other's. */
.band-rule {
  position: relative;
}

.band-rule::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  background: linear-gradient(
    to right,
    var(--accent) 0 var(--accent-run, 24%),
    var(--rule-inverse) var(--accent-run, 24%)
  );
}

.band-rule--reversed::after {
  background: linear-gradient(
    to right,
    var(--rule-inverse) 0 var(--accent-run, 62%),
    var(--accent) var(--accent-run, 62%)
  );
}

/* ---------------------------------------------------------------------- */
/* Buttons                                                                  */
/* ---------------------------------------------------------------------- */

.btn-accent {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s1);
  background: var(--accent-deep);
  color: #FFFFFF;
  border: none;
  border-radius: var(--r-sm);
  padding: var(--s2) var(--s3);
  font-family: var(--font-ui);
  font-weight: 600;
  box-shadow: var(--shadow-1);
  cursor: pointer;
  transition: transform var(--fast) var(--ease),
              box-shadow var(--fast) var(--ease),
              background-color var(--fast) var(--ease);
}

.btn-accent:hover {
  /* Darker still than the resting fill (#B23800, 6.07:1 with white) so
     hover stays comfortably clear of the 4.5:1 floor alongside the lift
     and shadow. */
  background: #B23800;
  box-shadow: var(--shadow-2);
  transform: translateY(-1px);
  color: #FFFFFF;
}

.btn-quiet {
  display: inline-flex;
  align-items: center;
  gap: var(--s1);
  background: transparent;
  color: var(--ink-700);
  border: none;
  padding: var(--s2) 0;
  font-family: var(--font-ui);
  font-weight: 600;
  background-image: linear-gradient(var(--ink-900), var(--ink-900));
  background-repeat: no-repeat;
  background-position: 0 100%;
  background-size: 0% 2px;
  border-radius: 0;
  cursor: pointer;
  transition: background-size var(--fast) var(--ease), color var(--fast) var(--ease);
}

.btn-quiet:hover {
  background-size: 100% 2px;
  color: var(--ink-900);
}

/* ---------------------------------------------------------------------- */
/* Device frames                                                           */
/* ---------------------------------------------------------------------- */

.frame-browser {
  border-radius: var(--r-lg);
  background: var(--surface);
  box-shadow: var(--shadow-2);
  overflow: hidden;
  border: 1px solid var(--line);
}

.frame-browser::before {
  content: "";
  display: block;
  height: 36px;
  background: var(--surface-sunken);
  border-bottom: 1px solid var(--line);
  background-image:
    radial-gradient(circle 5px, var(--stop) 5px, transparent 5.5px),
    radial-gradient(circle 5px, #E8A94D 5px, transparent 5.5px),
    radial-gradient(circle 5px, var(--go) 5px, transparent 5.5px);
  background-repeat: no-repeat;
  background-position: 16px center, 34px center, 52px center;
}

.frame-browser__body {
  display: block;
}

.frame-phone {
  position: relative;
  width: 280px;
  border-radius: var(--r-xl);
  background: var(--ink-900);
  padding: 14px;
  box-shadow: var(--shadow-2);
}

.frame-phone::before {
  content: "";
  position: absolute;
  top: 14px;
  left: 50%;
  transform: translateX(-50%);
  width: 90px;
  height: 22px;
  background: var(--ink-900);
  border-radius: 0 0 var(--r-sm) var(--r-sm);
  z-index: 1;
}

.frame-phone__body {
  display: block;
  border-radius: calc(var(--r-xl) - 8px);
  overflow: hidden;
  background: var(--surface);
}

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

/* One reveal system, parameterized rather than forked: the offset is two
   custom properties whose defaults are the old 0/16px, so every existing
   `.reveal` behaves exactly as it did and a block that wants to arrive from
   a different direction sets `--reveal-x` / `--reveal-y` on itself instead
   of needing a second class, a second transition and a second entry in the
   reduced-motion block below. reveal.js is untouched: it still only adds
   `.is-in`, and still returns early under reduced motion. */
html.js .reveal {
  opacity: 0;
  transform: translate(var(--reveal-x, 0), var(--reveal-y, 16px));
  transition: opacity 500ms var(--ease), transform 500ms var(--ease);
}

html.js .reveal.is-in {
  opacity: 1;
  transform: translate(0, 0);
}

/* ---------------------------------------------------------------------- */
/* Reduced motion                                                          */
/* ---------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  html.js .reveal {
    opacity: 1;
    transform: none;
    transition: none;
  }

  a,
  .btn-accent,
  .btn-quiet {
    transition: none;
  }

  .btn-accent:hover {
    transform: none;
  }
}
