/* ============================================================
   Deviceland — design tokens
   ============================================================

   Custom properties ONLY. This file is loaded on every page, including the
   WooCommerce ones, so it must not contain a single element selector.

   deviceland.css — the homepage stylesheet — opens with a reset that restyles
   body, html, a, ul, img, button and *. Loading that site-wide repainted the
   whole store cream and stripped list bullets and link colour out of Elessi's
   templates. The tokens are the part that is safe to share; the reset is not.
   ============================================================ */

:root {
  /* Ink is a warm espresso, not black. It is a text and control colour only —
     no section on this page uses it as a ground. A black band is the cheapest
     way to look expensive and the fastest way to look like everyone else. */
  --ink: #241a13;
  --ink-2: #2e2219;
  --ink-3: #3a2c20;
  --ink-4: #4a382a;
  --on-ink: #fff9f5;

  /* The paper rungs carry roughly twice the warmth they used to, and the
     reason is a compliance constraint rather than a change of taste.

     Apple's channel guidelines forbid grading, relighting or recolouring a
     product photo, and forbid producing our own. Every warm cream ground on
     this page used to arrive baked into the product renders themselves — so
     the paper only had to be warm enough not to argue with them, and at
     #faf7f2 it measured a red-to-blue spread of 8, which is very nearly
     neutral. With the renders replaced by untouched packshots on white, there
     is nothing left to carry the warmth but the paper, so the paper carries
     it: spread 8 -> 16 on --paper, 13 -> 20 and 18 -> 26 below it.

     The second job these values do is make white behave. Apple product photos
     may sit only on "a white, off-white, light gray, or black background", so
     a white well is unavoidable — and a white well on a near-white ground
     reads as a hole in the page rather than as a plate. The old ladder was
     unusable for that: white, --bone, --surface and --paper spanned 100.00%,
     97.28%, 95.72% and 93.26% relative luminance, four rungs inside seven
     points, none of them separable by eye. The new ladder spreads out:

       #fff       100.00%   the packshot well, always
       --bone      97.28%   mat for a --surface band
       --surface   95.72%   tile ground; mat for a --paper band
       --paper     89.29%   ground; mat for a --paper-2 band
       --paper-2   83.57%   ground for bands that hold wells
       --paper-3   75.90%   footer
       --sand      71.64%   chapter break

     The rule for framing a packshot is one line: THE MAT IS THE NEXT RUNG
     ABOVE THE BAND, AND THE WELL IS ALWAYS WHITE. Picking mat values by eye
     instead measured wrong twice — --bone on a --paper-2 band left the well
     only 2.72 points above its own mat, below the ~2-3 points that is
     reliably visible, so the plate had no frame and card and well read as one
     white block. One rung up lands the mat near the midpoint and both steps
     stay legible: 89.29 -> 95.72 -> 100.00, or 83.57 -> 89.29 -> 100.00.

     Warming lowers luminance, so every dark-on-paper ratio moves down about
     0.8 and none of them come close to mattering: --on-paper is 16.89:1 on
     --paper, the worst of the four inks is 12.27:1. See --muted-fg below for
     the one token that did need solving. */
  --paper: #f8f2e8;
  --paper-2: #f2ebde;
  --paper-3: #eae1d0;
  /* oklch(99% .004 90) — a step lighter than paper, for the picks ground */
  /* Top of the ladder below white, and now unused as a band: the #floor
     section held it and a white well on it is a 2.72-point step, which is
     invisible. It is the correct mat for a --surface band if one is ever
     built; it is the wrong mat for everything currently on the page. */
  --bone: #fdfcf8;
  --on-paper: #14110e;

  /* House grade for the documentary photography.

     Measured, the two photo sets were living in different worlds:

                     warm   sat   lum   cool px
       studio/bento    39    75    83     2%
       documentary      8    32    49    24%

     A 34-point luminance drop and a halved saturation is what reads as
     "sophisticated, then suddenly not". This closes the gap to warm 21 /
     sat 47 / lum 57 / cool 15% — it does not erase it, because a workbench
     photographed under shop lighting should not pretend to be a seamless
     cove, and pushing it to 83% luminance would only look blown out.

     This is grading context, not merchandise. See the note in the README:
     never grade a photograph where the product's true colour is the thing
     being sold; always grade the room around it. */
  --grade-real: brightness(1.18) contrast(0.93) saturate(0.82) sepia(0.34);
  /* Same family, for photographs that are already high-key. --grade-real lifts
     exposure 18%, which is right for a mid-key bench shot and destructive on a
     bright one. Measured share of pixels clipped to pure white:

                        raw    --grade-real   --grade-real-hi
       owner portrait   4.3%      25.9%            10.8%
       bench technician   0%      18.9%             2.9%
       storefront      13.8%      52.6%            32.2%

     A quarter of a portrait blown to paper-white is not a grade, it is a
     deletion — and faces are the one subject where losing modelling is
     immediately legible as "cheap photo". This variant raises contrast instead
     of exposure, and measures higher local contrast than --grade-real does
     (sd 86 vs 83) while keeping the same warm cast that unifies the set. */
  --grade-real-hi: contrast(1.1) saturate(0.9) sepia(0.22);

  /* Third tier, for the sunlit exterior. The storefront is a bright outdoor
     photograph at lum 173 — 40 points above anything shot indoors — and both
     of the grades above make it worse, not better:

                              lum   blown to pure white
       raw                    173         13.8%
       --grade-real           197         52.6%   <- over half the building
       --grade-real-hi        181         32.2%
       --grade-real-sun       169          7.0%

     Over half a photograph clipped to paper-white is not a warm cast, it is
     lost architecture — the brick, the awning edge and the window frames stop
     existing. The correction a bright exterior needs is negative: hold it
     DOWN, add a little contrast, and carry the same warm cast (warm +26 here
     against +27 for --grade-real, so it still reads as the same family). */
  --grade-real-sun: brightness(0.93) contrast(1.04) saturate(0.9) sepia(0.22);

  /* The one chapter break. Warm sand carries the same structural job a dark
     band was doing — it separates — without borrowing gravity it hasn't earned. */
  --sand: #e7dbc4;
  --sand-2: #dccdb0;
  --on-sand: #2a2018;

  /* Tailwind `bg-surface` in the reference. The category photographs carry a
     warm cream ground of their own (sampled: 251-255, 237-248, 209-227), so
     the tile colour has to sit just above the lightest of them or the scrim
     shows a seam where it lands. */
  --surface: #fdfaf3;
  --surface-80: rgba(253, 250, 243, 0.8);
  /* Was #7d7365, which measured 4.36:1 on --paper and 4.46:1 on --surface —
     under the 4.5:1 AA floor on both. This token carries .tile-price, so the
     failing text was the price: "From $999" was the least legible thing on a
     retail tile. #756b5d is the smallest darkening that clears AA on the
     lighter of the two grounds (4.89:1 paper, 5.01:1 surface) and so keeps the
     warm grey reading the original was tuned for. */
  /* #756b5d was then solved against the two LIGHTEST grounds only, and this
     token is used on all seven. Re-measured across every ground it actually
     lands on, it was already failing two of them before the paper rungs
     moved:

                        #756b5d        #696053
       --surface          5.01           5.93
       --paper            4.89           5.55
       --paper-2          4.53           5.21
       --paper-3          4.14  FAIL     4.76
       --sand             3.82  FAIL     4.51

     --paper-3 is the footer and --sand is the whole service chapter, so the
     failure was live on two full-width bands, not an edge case. #696053 is
     the lightest value on the same hue line (g/r 0.915, b/r 0.795, so the
     warm grey character is preserved exactly) that clears 4.5:1 on --sand,
     which is the darkest ground this token is ever set on. Solving for the
     darkest rather than the lightest ground is the correction; every other
     pairing then passes with margin. */
  --muted-fg: #696053;

  /* Sampled from the official Deviceland lockup: #c95928 */
  --ember: #c95928;
  --ember-lift: #e4713c;
  --ember-deep: #9c3f18;
  --on-ember: #fff9f5;
  /* Large saturated fields need more light and more chroma than a 40px mark:
     the logo value spread across a whole section reads as rust, not energy. */
  --ember-field: #d9601f;
  --ember-field-2: #b8410f;

  --sans: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
    Helvetica, Arial, sans-serif;
  --display: "Instrument Serif", "Times New Roman", Georgia, serif;

  /* Named for the band they belong to, not the value they used to hold: these
     are the hairlines on the sand chapter and the footer. */
  --hair-dark: rgba(42, 32, 24, 0.15);
  --hair-dark-2: rgba(42, 32, 24, 0.28);
  --hair-light: rgba(20, 17, 14, 0.11);
  --hair-light-2: rgba(20, 17, 14, 0.22);

  --sh-1: 0 1px 2px rgba(20, 17, 14, 0.05),
    0 8px 20px -12px rgba(20, 17, 14, 0.2);
  --sh-2: 0 2px 4px rgba(20, 17, 14, 0.04),
    0 26px 50px -26px rgba(20, 17, 14, 0.26);
  --sh-3: 0 3px 6px rgba(20, 17, 14, 0.05),
    0 50px 90px -44px rgba(20, 17, 14, 0.42);
  --sh-prod: 0 34px 44px -28px rgba(20, 17, 14, 0.55),
    0 90px 120px -70px rgba(20, 17, 14, 0.4);

  /* max-w-[1480px] px-6 lg:px-10 — 1400px of content at desktop, which is what
     the reference's column arithmetic is built on. At 1400/40 the bento
     columns come out 27px narrow and nothing else lines up either. */
  --shell: 1480px;
  --gut: 24px;

  --ease: cubic-bezier(0.16, 1, 0.32, 1);
  --ease-io: cubic-bezier(0.65, 0, 0.35, 1);

  /* Motion budget. Ding & Kyung (2025): moderate speed beats both fast and
     slow. Nothing on this page animates outside this band. */
  --dur-micro: 150ms;
  --dur-ui: 250ms;
  --dur-reveal: 450ms;
  --dur-slow: 700ms;

  /* 96px was the height the three-part brand lockup needed — monogram,
     DEVICELAND, and the tagline stacked under it at 52px. The header now
     carries the wordmark alone, so the bar can be the height a single line of
     type wants rather than the height a stacked lockup forced. 74px at rest,
     66px condensed.
     This propagates: .hero-stack reserves --head-h for the masthead and takes
     100svh minus it, and html has scroll-padding-top off --head-h-stuck, so
     the hero grows by the 22px the header gives back with no other change. */
  /* The header is now TWO rows: the bar, and the folio of credentials under
     it. They are split into separate tokens because they are consumed
     differently.

       --bar-h     the masthead bar itself. Only .masthead-inner uses it.
       --folio-h   the credential row: dateline, Premier Partner, Authorized
                   Service Provider, live hours, no appointment.
       --head-h    bar + folio. This is what the ~14 `calc(var(--head-h) + N)`
                   rules across five stylesheets use to clear the fixed
                   header, so defining it as the sum means every one of them
                   clears BOTH rows with no edit.

     --head-h-stuck stays the bar alone: the folio collapses to zero height on
     scroll. The credentials do their work in the opening view, and a
     persistent 40px band of small caps over the content would be chrome for
     its own sake. */
  --bar-h: 74px;
  /* 0: the credential row was removed from the masthead (Oct 11). */
  --folio-h: 0px;
  --head-h: calc(var(--bar-h) + var(--folio-h));
  --head-h-stuck: 66px;

  /* ----------------------------------------------------------------------
     Radius: SQUARED. The house geometry is 2px, not pills.
     ----------------------------------------------------------------------
     The previous scale was 10 / 16 / 22 / 28 / 24 / 999px across 128
     border-radius declarations, and --r-pill alone accounted for 50 of
     them — the most-used radius in the codebase. That is a consumer-tech
     geometry and it fought everything else on the page.

     Three reasons to square it:

       1. Hermes, The Row, Celine and Loro Piana all sit at 0-2px with
          hairline borders. Radii in the 10-28px range read friendly, not
          expensive.
       2. This design is built on Instrument Serif, tracked small caps and
          1px rules. A 999px pill contradicts every one of those.
       3. Apple's own interface is pills and generous radii. A reseller
          pairing Apple's button shape WITH Apple-adjacent typography drifts
          toward the "do not imitate Apple's website" line. Squaring is
          simultaneously the more elevated and the more compliant choice.

     2px rather than 0: a true zero reads as unstyled, 2px reads as decided.

     MIGRATION NOTE. --r-pill is REDEFINED rather than removed. Hunting 50
     call sites across six stylesheets invites a missed one; redefining the
     token flips all of them atomically, and any future code that asks for
     "pill" gets the house shape instead of reintroducing a capsule. The
     name is now a deliberate misnomer, kept only to avoid that rename —
     which is what this paragraph exists to record.

     Circular status dots keep border-radius:50% and are unaffected: those
     are dots, not containers. --r-sm stays the largest step so photographs
     and plates read crisp rather than hard-cornered. */
  --r-sm: 2px;
  --r-md: 3px;
  --r-lg: 4px;
  --r-xl: 4px;
  --r-pill: 2px;
  --r-tile: 3px;

  /* Elevation: four steps. The same audit found ten distinct box-shadows, nine
     of them used exactly once — ten unrelated "heights" with no logic, which is
     the difference between a design system and accumulated CSS.

     Each step is two shadows: a tight contact shadow for the object's own edge,
     and a wide diffuse one for the distance it sits above the paper. Warm-tinted
     rather than neutral black, because a cream ground reflects warm light. */
  --e1: 0 1px 2px rgba(20, 17, 14, 0.05), 0 1px 1px rgba(20, 17, 14, 0.04);
  --e2: 0 2px 4px rgba(20, 17, 14, 0.05), 0 8px 18px -8px rgba(20, 17, 14, 0.18);
  --e3: 0 3px 6px rgba(20, 17, 14, 0.05), 0 20px 40px -18px rgba(20, 17, 14, 0.3);
  --e4: 0 6px 14px rgba(20, 17, 14, 0.06), 0 44px 80px -36px rgba(20, 17, 14, 0.42);
  /* On the ember ground the same steps need a warmer, deeper tint or they read
     as grey smudges rather than shadow. */
  --e3-warm: 0 3px 6px rgba(60, 20, 0, 0.12), 0 20px 40px -18px rgba(60, 20, 0, 0.4);
  --e4-warm: 0 6px 14px rgba(60, 20, 0, 0.14), 0 44px 80px -36px rgba(60, 20, 0, 0.5);

  /* Ember grounds that carry small text now use the existing --ember-deep
     (#9c3f18), which measures 6.42:1 against white. The bright --ember tops out
     at 3.94:1, meaning NOTHING small could reach the 4.5:1 AA threshold on it
     at any opacity — the ground itself was the failure, not the text colour.
     --ember stays as the accent on light paper, where it has contrast to spare. */
  --ember-deep-2: #7d310f;
}

@media (min-width: 1024px) {
  :root {
    --gut: 40px;
  }
}

/* Phone header height lives HERE, not in deviceland.css.
   ------------------------------------------------------------
   deviceland.css lowered --head-h to 76px for short phones, inside
   `:root { }` within a media query. chrome.css — the sheet a WooCommerce page
   loads instead of deviceland.css — is generated with a selector whitelist
   that does not include :root, so that override was silently dropped. The
   homepage got a 76px header on a phone and the product page kept 96px, which
   is why the two islands were different heights on the same device.

   tokens.css is loaded by every page, so a token override belongs in it. The
   value is also no longer gated on viewport HEIGHT: 76px is simply the phone
   header, which is the shorter of the two and the one that reads better.

   --head-h-stuck matches --head-h deliberately. The phone header does not
   change height on scroll, and letting the pair disagree is what once made the
   bar grow 2px while a height transition ran. */
@media (max-width: 767px) {
  :root {
    --head-h: 76px;
    --head-h-stuck: 76px;
  }
}
