/* ==========================================================================
   PLANQ DESIGN TOKENS
   --------------------------------------------------------------------------
   The single source of truth for every colour, size, space, radius, shadow,
   z-index and motion value used by the PLANQ website.

   RULES
   1. No hex colour value may appear in any other stylesheet in this theme.
   2. No spacing value may be used that is not on the scale below.
   3. Every colour intended for text carries its measured contrast ratio in a
      comment. WCAG 2.1 AA requires 4.5:1 for body text, 3:1 for large text
      (>=24px, or >=18.66px bold) and 3:1 for UI component boundaries.
   4. If a value needs to change, it changes here.

   Ratios were computed with the WCAG 2.1 relative-luminance formula.
   Documented in docs/planq-redesign/phase-1/03_DESIGN_TOKENS.md
   ========================================================================== */

:root {

  /* ======================================================================
     1. BRAND
     ----------------------------------------------------------------------
     #c10404 was already present in PLANQ's configuration, where it was set
     as the *discount* colour. It is measurably the strongest red the brand
     owns and it is promoted here to primary. Phase 0 doc 24, finding 3.
     ====================================================================== */

  --planq-brand:            #c10404;  /* 6.39:1 on white. Primary. Text and buttons both pass */
  --planq-brand-dark:       #a30303;  /* 8.17:1 on white. Hover */
  --planq-brand-darker:     #8a0202;  /* 10.05:1 on white. Active / pressed */
  --planq-brand-subtle:     #fdf1f1;  /* Tinted background only. Never text, never a border */
  --planq-brand-on:         #ffffff;  /* Text placed on --planq-brand. 6.39:1 */

  /* Brand red measures only 2.96:1 against --planq-ink-900, so it is NOT used
     for text or borders on dark surfaces. On dark, accent with white instead. */

  /* ======================================================================
     2. INK  (text and iconography)
     ====================================================================== */

  --planq-ink-900:          #111111;  /* 18.88:1 on white. Headings, spec values */
  --planq-ink-800:          #333333;  /* 12.63:1 on white. Body copy */
  --planq-ink-600:          #595959;  /*  7.00:1 on white. Secondary text, table labels */
  --planq-ink-500:          #767676;  /*  4.54:1 on white. Minimum for text. NOT valid on --planq-surface-2 (4.24:1, measured) */
  --planq-ink-400:          #8c8c8c;  /*  3.36:1 on white. NON-TEXT ONLY: icons, disabled states */

  --planq-text:             var(--planq-ink-800);
  --planq-text-strong:      var(--planq-ink-900);
  --planq-text-secondary:   var(--planq-ink-600);
  --planq-text-muted:       var(--planq-ink-500);

  /* ======================================================================
     3. SURFACES
     ====================================================================== */

  --planq-surface:          #ffffff;  /* Page */
  --planq-surface-2:        #f7f7f7;  /* Cards, table zebra rows, quiet sections */
  --planq-surface-3:        #f2f2f2;  /* Recessed wells, image frames */
  --planq-surface-elevated: #ffffff;  /* Dropdowns, popovers. Separated by border, not shadow */

  /* Dark surfaces (footer, utility bar) */
  --planq-surface-inverse:      #111111;
  --planq-surface-inverse-2:    #1c1c1e;  /* Elevated block on dark */
  --planq-text-inverse:         #ffffff;  /* 18.88:1 on --planq-surface-inverse */
  --planq-text-inverse-muted:   #a3a3a3;  /*  7.48:1 on --planq-surface-inverse */

  /* ======================================================================
     4. BORDERS
     ----------------------------------------------------------------------
     The split matters. A #d9d9d9 input border on white measures 1.41:1 and
     fails WCAG 1.4.11, which requires 3:1 for the boundary of any control.
     ====================================================================== */

  --planq-border:            #d9d9d9;  /* 1.41:1. DECORATIVE dividers only */
  --planq-border-soft:       #e8e8e8;  /* Very quiet rules inside cards */
  --planq-border-strong:     #949494;  /* ~3.0:1. REQUIRED for inputs, checkboxes, control outlines */
  --planq-border-inverse:        #333333;  /* Decorative divider on dark */
  --planq-border-strong-inverse: #6b6b6b;  /* 3.56:1 on dark. Functional borders on dark */

  /* ======================================================================
     5. SEMANTIC
     ====================================================================== */

  --planq-success:          #3f7419;  /* 5.64:1 on white, 5.64:1 for white on it */
  --planq-success-dark:     #2f5a12;  /* 8.10:1 */
  --planq-success-subtle:   #f1f6ec;
  --planq-warning:          #8a5200;  /* 6.39:1. Backorder, lead time */
  --planq-warning-subtle:   #fdf5e9;
  --planq-danger:           #b3261e;  /* 6.54:1. Errors, out of stock. Deliberately lighter than brand */
  --planq-danger-dark:      #8f1e17;  /* 8.55:1. Hover on a destructive button */
  --planq-danger-subtle:    #fdf0ef;
  --planq-link:             #0b5cab;  /* 6.70:1. Chosen by PLANQ, not inherited from WordPress */
  --planq-link-hover:       #084684;

  /* Scrims. Derived from --planq-ink-900, but a custom property cannot be
     interpolated into rgba(), so the channels are written out here — once —
     rather than as literals at each use site. */
  --planq-scrim:            rgba(17, 17, 17, 0.55);  /* Modal and off-canvas backdrop */
  --planq-scrim-strong:     rgba(17, 17, 17, 0.72);  /* Label over product photography */

  /* ======================================================================
     6. FOCUS
     ----------------------------------------------------------------------
     Phase 0 found no detectable focus indicator anywhere (WCAG 2.4.7).
     ====================================================================== */

  --planq-focus:            #0b5cab;
  --planq-focus-inverse:    #ffffff;
  --planq-focus-width:      3px;
  --planq-focus-offset:     2px;

  /* ======================================================================
     7. SPACING  (4px base)
     ====================================================================== */

  --planq-space-1:    4px;
  --planq-space-2:    8px;
  --planq-space-3:   12px;
  --planq-space-4:   16px;
  --planq-space-5:   24px;
  --planq-space-6:   32px;
  --planq-space-7:   48px;
  --planq-space-8:   64px;
  --planq-space-9:   96px;
  --planq-space-10: 128px;

  /* Section rhythm.
     The two modifier values are tokens rather than literals in layout.css so
     that the mobile block at the end of this file can scale all three together.
     They were literals, and the effect showed up in Phase 2 validation: at
     375px every section had dropped to 48px of padding except the closing
     section, which kept its desktop 128px and read as a gap rather than an
     ending. */
  --planq-section-y:        var(--planq-space-9);
  --planq-section-y-mobile: var(--planq-space-7);
  --planq-section-y-tight:  var(--planq-space-7);
  --planq-section-y-loose:  var(--planq-space-10);

  /* ======================================================================
     8. LAYOUT
     ====================================================================== */

  --planq-page-max:     1320px;  /* Outer container */
  --planq-content-max:  1160px;  /* Standard content */
  --planq-wide-max:     1520px;  /* Full-bleed-ish media */
  --planq-read-max:       68ch;  /* Prose measure */

  --planq-gutter:        16px;
  --planq-gutter-md:     24px;
  --planq-gutter-lg:     32px;

  --planq-header-h:            72px;
  --planq-header-h-sticky:     60px;
  --planq-header-h-mobile:     60px;
  --planq-header-bottom-h:     46px;
  --planq-topbar-h:            34px;

  /* Flatsome makes .header-wrapper position:fixed once the page scrolls and
     collapses it. The collapsed height is NOT one number, and this token was
     shipped as if it were: 107px was measured at 1440 only (Phase 5 doc 01
     finding B13) and the comment here promised a re-measurement per viewport.
     Done, with `VW=<w> HOLD=1200 node tools/phase5/structure.js /`, reading the
     .header-wrapper.stuck box:

         vw     header#header     .header-wrapper.stuck
         375         61                   61
         480         61                   61
         768         73                   61
         849         73                   61
         850        163                  107
         1024       163                  107
         1280       163                  107
         1440       163                  107

     So there are two states, not a gradient, and the boundary is exactly
     Flatsome's large breakpoint -- 849 gives 61, 850 gives 107. That is the same
     boundary the mobile block at the end of this file already uses, so the
     correction is one line there rather than a new media query.

     The token feeds --planq-anchor-offset, which is now the ONE anchor offset on
     the site (reset.css). Below 850 the old 107 over-reserved by 46px. */
  --planq-header-stuck-h:      107px;
  /* THE RESERVE IS space-5 AND NOT space-4 BECAUSE IT HAS TO ABSORB THE REVEAL
     SHIFT. Measured at 1440 with ANCHOR mode: a jump to #planq-pillars, already
     revealed because it sits inside the first scroll, landed with its top exactly
     123px down -- the declared offset, gap 16 under the pinned header. A jump to
     #planq-showcase, #planq-support or #planq-closing landed at 111, gap 4.

     The 12px difference is --planq-reveal-shift. An unrevealed section is
     translated 12px DOWN when the browser computes where to scroll, so the scroll
     lands 12px lower than it otherwise would; the IntersectionObserver then fires,
     the transform is removed, and the section rises 12px into the header's
     clearance. Motion and anchor scrolling interacting, in the one direction that
     eats the clearance rather than adding to it.

     16px still cleared it, by 4px, and only because 16 happens to be larger than
     12. That is a coincidence, not a design, and the next change to either token
     could invert it silently. 24px states the dependency instead: the reserve is
     larger than the shift on purpose, and the worst measured landing is 12px
     clear rather than 4. Phase 5 doc 09 section 5. */
  --planq-anchor-offset:       calc(var(--planq-header-stuck-h) + var(--planq-space-5));

  /* Lift join. ONE number, used twice: the section above a lifted panel
     reserves exactly this much extra padding-block-end, and the panel is
     pulled up by exactly this much. Collision is therefore impossible by
     construction rather than by inspection, and that guarantee is the whole
     reason this is a token and not two literals. Phase 5 doc 04 section 4.3. */
  --planq-lift:                var(--planq-space-8);

  /* ======================================================================
     9. TYPOGRAPHY
     ----------------------------------------------------------------------
     Lato is retained. It is already self-hosted by Kirki, it is a competent
     neutral humanist sans, and replacing it would cost a font-loading
     exercise for no measurable gain. The problem was the absence of a scale
     (0 font_size keys existed) and the presence of a handwriting accent face.
     A 1.25 ratio on a 16px base.
     ====================================================================== */

  --planq-font:       Lato, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --planq-font-mono:  ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;

  --planq-text-2xs:  0.750rem;  /* 12px — legal, footnotes */
  --planq-text-xs:   0.875rem;  /* 14px — captions, table labels, eyebrows */
  --planq-text-sm:   0.9375rem; /* 15px — secondary body, navigation */
  --planq-text-base: 1rem;      /* 16px — body. NEVER smaller for prose */
  --planq-text-lg:   1.125rem;  /* 18px — lead paragraph */
  --planq-text-xl:   1.25rem;   /* 20px — h4, card titles */
  --planq-text-2xl:  1.5625rem; /* 25px — h3 */
  --planq-text-3xl:  1.9375rem; /* 31px — h2 */
  --planq-text-4xl:  2.4375rem; /* 39px — h1 */
  --planq-text-5xl:  3.0625rem; /* 49px — hero h1 */
  --planq-text-6xl:  3.8125rem; /* 61px — display */

  --planq-leading-tight:   1.1;
  --planq-leading-snug:    1.25;
  --planq-leading-heading: 1.15;
  --planq-leading-normal:  1.6;
  --planq-leading-relaxed: 1.7;

  /* Exactly two Lato faces are self-hosted, verified in the served page's
     @font-face rules: normal 400 and normal 700, each declared twice for the
     latin and latin-ext unicode ranges (the four .woff2 files in
     wp-content/fonts/lato/). No italic face and no 900 face is loaded.

     So --planq-weight-medium and --planq-weight-heavy do NOT get their own
     face: CSS font matching resolves a request above 500 to the heaviest
     available, which is the real 700 file, with no synthetic bolding. They are
     kept as named roles rather than collapsed into 700 so that adding a face
     later is a one-line change here. The display role therefore separates
     itself from h1 by SIZE and TRACKING, not by weight — do not add a 900 face
     just to make that distinction; it costs a request for a difference nobody
     asked for. */
  --planq-weight-regular:  400;  /* real face */
  --planq-weight-medium:   600;  /* resolves to the 700 face */
  --planq-weight-bold:     700;  /* real face */
  --planq-weight-heavy:    900;  /* resolves to the 700 face */

  --planq-tracking-tight:   -0.02em;
  --planq-tracking-normal:  0;
  --planq-tracking-wide:    0.02em;
  --planq-tracking-eyebrow: 0.08em;

  /* ======================================================================
     10. RADIUS
     ----------------------------------------------------------------------
     Restrained on purpose. PLANQ should read as precise and engineered, not
     as consumer software. --planq-radius-full is for status pills only.
     ====================================================================== */

  --planq-radius-sm:    2px;   /* Inputs, badges, small controls */
  --planq-radius-md:    4px;   /* Buttons, cards */
  --planq-radius-lg:    6px;   /* Panels, dropdowns, media frames */
  --planq-radius-full:  999px; /* Status pills only */

  /* ======================================================================
     11. ELEVATION
     ----------------------------------------------------------------------
     The default is no shadow. Borders are the primary means of separation.
     Shadow-heavy interfaces read as consumer software, not hardware.
     ====================================================================== */

  --planq-elev-0:       none;
  --planq-elev-1:       0 1px 2px rgba(17, 17, 17, 0.08);
  --planq-elev-2:       0 4px 12px rgba(17, 17, 17, 0.10);
  --planq-elev-overlay: 0 8px 28px rgba(17, 17, 17, 0.14);

  /* ======================================================================
     12. Z-INDEX
     ====================================================================== */

  --planq-z-base:      0;
  --planq-z-raised:    1;
  --planq-z-sticky:  100;
  --planq-z-header:  200;
  --planq-z-dropdown:300;
  --planq-z-overlay: 400;
  --planq-z-modal:   500;
  --planq-z-toast:   600;
  --planq-z-skiplink:700;

  /* ======================================================================
     13. MOTION
     ----------------------------------------------------------------------
     Subtle. Only opacity, transform, colour and border-colour are animated.
     All of it is disabled under prefers-reduced-motion (see reset.css).
     ====================================================================== */

  /* THREE LAYERS, THREE DURATION BANDS, AND THE BANDS DO NOT OVERLAP.
     PHASE 6. Doc 02 divides all motion on this site into exactly three layers and
     gives each its own band, because a page whose hover feedback is as slow as its
     scroll reveals feels broken rather than calm:

       LAYER 3  INTERACTION  120-180ms  dur-1, dur-2   hover, focus, menus
       LAYER 2  REVEAL       320-640ms  dur-4..dur-6   scroll-triggered
       LAYER 1  ENTRANCE     320-640ms  dur-4..dur-6   first paint, once

     dur-3 (240ms) sits between the bands and is Phase 2/5 legacy: it is still the
     duration of the old section-level reveal and of several card transitions. New
     work picks a band deliberately rather than reaching for dur-3.

     THE RULE THAT MATTERS MOST: interaction is always faster than decoration.
     Section 9 of the Phase 6 brief requires it, and doc 04 section 5.1 makes it
     checkable -- every navigation transition <= 180ms, every reveal >= 320ms. That
     is gate 11 in doc 04 section 8, asserted from computed style rather than from
     this comment. */
  --planq-dur-1:    120ms;
  --planq-dur-2:    180ms;
  --planq-dur-3:    240ms;
  --planq-dur-4:    320ms;
  --planq-dur-5:    480ms;
  --planq-dur-6:    640ms;
  --planq-ease:     cubic-bezier(0.2, 0, 0.2, 1);
  --planq-ease-out: cubic-bezier(0, 0, 0.2, 1);

  /* The entrance curve, and why it is not --planq-ease-out.
     A decelerating cubic-bezier(0, 0, 0.2, 1) is right for a 120ms hover: it starts
     at full speed, which reads as responsive. An element travelling 24px over 480ms
     with that curve spends most of its time nearly stationary at the end, which
     reads as sluggish. This curve covers most of its distance early and settles,
     which is what makes an entrance feel like arrival rather than drift. It is NOT
     a spring -- it never overshoots 1, so nothing bounces. Doc 02 section 11.2
     rejects overshoot easing explicitly. */
  --planq-ease-entrance: cubic-bezier(0.16, 1, 0.3, 1);

  /* Reveal parameters. Both were literals inside home.css; they are tokens now
     because Phase 5 reuses them for a staggered child reveal and two literals
     in two files drift. The step is capped in home.css via an nth-child rule, and
     the cap is asserted in both a comment and a selector -- keeping the unit in one
     place is what lets the two agree. Phase 5 doc 06 section 2.2.

     PHASE 6 RAISED THE STEP FROM 60ms TO 80ms, AND THAT IS A MEASURED CHANGE.
     tools/phase6/framediff.js found the busiest pair of consecutive load frames
     differed in 0.1111% of a 1440x1 crop, and motionreality.js found travelY across
     the whole page was {12: 47} -- forty-seven elements, one distance, one
     direction. A 60ms step on a 240ms transition means consecutive items overlap by
     75%, so a seven-item stagger reads as one simultaneous fade. 80ms on a 320ms
     transition still overlaps, but the eye can resolve the order, which is the
     entire point of a stagger. Doc 02 section 6.5.

     --planq-reveal-shift KEEPS ITS NAME AND ITS 12px. It is the old distance, still
     used by the Phase 2 section-level reveal, and the reduced-motion block at the
     end of this file zeroes it BY NAME. Renaming it would silently un-fix that. */
  --planq-reveal-step:   80ms;
  --planq-reveal-shift:  12px;

  /* Phase 6 reveal geometry. Doc 02 section 6 defines five patterns and these are
     their parameters. Every one of them is zeroed or neutralised in the
     reduced-motion block at the end of this file -- see the note there about
     clip-path, which is the one a zeroed duration does not fix. */
  --planq-reveal-shift-lg: 24px;   /* reveal-up: far enough to be seen at 320ms */
  --planq-reveal-shift-x:  28px;   /* reveal-side: horizontal, >=850 only */
  --planq-reveal-scale:    0.97;   /* reveal-scale: 3%, below the blur threshold */

  /* The entrance step. Layer 1 delays are integer multiples of this, so the whole
     first-paint sequence retimes from one value. Doc 02 section 5.1 sketched
     90/180/300/420/540ms; the implementation uses exact multiples -- 90/180/270/
     360/450 -- because a sequence built from one token cannot drift out of order,
     and 30ms of difference at position four is not a perceptual distinction.
     Recorded as an implementation refinement in doc 05. */
  --planq-entrance-step:   90ms;

  /* ======================================================================
     14. CONTROLS
     ====================================================================== */

  --planq-control-h:        44px;  /* Minimum touch target, both axes */
  --planq-control-h-sm:     36px;  /* Pointer-only contexts; still 44px tap area via padding */
  --planq-control-h-lg:     52px;
  --planq-control-px:       var(--planq-space-5);
  --planq-control-border:   1px;

  /* Product card image frame. A fixed ratio is what makes a catalogue read as
     one range: Phase 0 found originals from 1:1 to 1:24. */
  --planq-product-ratio:    4 / 3;
}

/* Mobile layout adjustments to layout-affecting tokens only. */
@media (max-width: 849px) {
  :root {
    --planq-section-y:       var(--planq-section-y-mobile);
    --planq-section-y-tight: var(--planq-space-6);
    --planq-section-y-loose: var(--planq-space-8);
    --planq-gutter-lg: var(--planq-gutter-md);

    /* Measured, not assumed: 61px at 375, 480, 768 and 849. See the table in
       section 8. --planq-anchor-offset reads this token, and because custom
       properties cascade per property, redefining it here is enough -- every
       calc() downstream re-resolves with no specificity contest. Same mechanism
       as the reduced-motion block at the end of this file. */
    --planq-header-stuck-h: 61px;
  }
}

/* --------------------------------------------------------------------------
   Reduced motion: zero the reveal parameters at source
   --------------------------------------------------------------------------
   PHASE 5, ADDED AFTER A MEASURED DEFECT. tools/reveal-check.js reported
   `reduced-motion ... max-item-delay=300ms`: under prefers-reduced-motion the
   sequenced grid items were correctly visible and correctly untransformed, and
   still carried the full 300ms transition-delay.

   WHY THE HOME.CSS RESET LOST. home.css neutralises the sequence with
   `.planq-reveal-seq > *` (0-1-0), but the delays are set by
   `.planq-reveal-seq > :nth-child(2)` and friends -- and :nth-child() is a
   pseudo-class, so those are 0-1-1. A MEDIA QUERY DOES NOT RAISE SPECIFICITY, a
   fact this theme has already been bitten by twice (responsive.css lines 31-53
   and 382-399, where a 0-1-0 rule inside a media query silently cancelled
   .planq-section--tight and --loose). The more specific rule won, exactly as the
   cascade says it should.

   WHY THIS IS THE FIX RATHER THAN A MORE SPECIFIC SELECTOR. Matching 0-1-1 in the
   reduced-motion block would work and would leave the next person one selector
   away from the same bug. Custom properties cascade PER PROPERTY, so zeroing the
   unit here makes every calc() that reads it resolve to 0s with no specificity
   contest at all, whatever selector set it. responsive.css:400 uses the identical
   mechanism for the landscape section tokens, so this is an existing pattern
   rather than a new one.

   REDUCED MOTION IS NOT A FASTER ANIMATION. A visible element that still waits
   300ms is motion's timing surviving without its movement -- content arriving
   late for no reason. reveal-check.js now asserts this, so the regression has a
   test and not just a comment. Phase 5 doc 06 section 7. */
/* PHASE 6 ADDS FIVE MORE TOKENS TO THIS BLOCK, AND ONE OF THEM IS THE INTERESTING
   ONE. --planq-reveal-scale goes to 1 rather than to 0, because it is a multiplier
   and not a distance: `scale(0)` is an element that has vanished. That is the kind
   of copy-paste this block invites, so it is written out.

   --planq-entrance-step is zeroed for the same reason --planq-reveal-step is: the
   Layer 1 sequence is built from integer multiples of it, and reset.css's
   reduced-motion rule sets animation-DURATION without touching animation-DELAY. A
   reduced-motion visitor would otherwise wait 450ms staring at an incomplete header
   while five entrance animations sat at their from-keyframe, each due to complete in
   a microsecond. Reduced motion is not a faster animation.

   WHAT THIS BLOCK STILL CANNOT FIX, AND WHERE THAT IS HANDLED. clip-path is not a
   distance, a duration or a delay, so no token here reaches it. A 640ms wipe from
   inset(0 0 100% 0) with a duration of 0.001ms is not a fast wipe -- it is a panel
   that is instantly and permanently clipped to nothing. The reset for that has to
   be `clip-path: none` on the element itself and it lives in home.css's own
   reduced-motion block. reset.css cannot help either: its reduced-motion rule names
   animation-duration, animation-iteration-count, transition-duration and
   scroll-behavior, and says nothing about clip-path or transition-delay. Doc 02
   section 8. */
@media (prefers-reduced-motion: reduce) {
  :root {
    --planq-reveal-step:     0s;
    --planq-reveal-shift:    0px;
    --planq-reveal-shift-lg: 0px;
    --planq-reveal-shift-x:  0px;
    --planq-reveal-scale:    1;
    --planq-entrance-step:   0s;
  }
}
