{
  "$meta": {
    "brand": "Goldstones Family Farm",
    "version": "1.0",
    "date": "2026-08-04",
    "cssFile": "tokens.css",
    "note": "Custom-property names match tokens.css and the exported CSS exactly. Values here are the source of truth for the build; anything not present must come back to Block 1 rather than being invented downstream.",
    "target": "Astro (output: server, @astrojs/cloudflare) on Cloudflare Workers, with Sanity CMS and Stripe Checkout. NOT WordPress — an earlier revision of this file named Divi 5 + WooCommerce, which was a superseded stack assumption and has been corrected."
  },

  "colour": {
    "cream":       { "var": "--gs-cream",       "hex": "#EFE9DB", "role": "primary background" },
    "creamAlt":    { "var": "--gs-cream-alt",   "hex": "#FBF8F0", "role": "DEPRECATED — alias of surface. #F7F2E8 was indistinguishable from #FBF8F0 as an adjacent surface, so alternating sections now use surface. Kept only so existing references resolve; do not use in new work." },
    "surface":     { "var": "--gs-surface",     "hex": "#FBF8F0", "role": "cards on cream" },
    "moss":        { "var": "--gs-moss",        "hex": "#4C5A3C", "role": "primary brand colour" },
    "mossDeep":    { "var": "--gs-moss-deep",   "hex": "#36422E", "role": "headings, dark panels, primary button" },
    "mossMid":     { "var": "--gs-moss-mid",    "hex": "#6B7A57", "role": "rules on moss, secondary button border" },
    "mossLight":   { "var": "--gs-moss-light",  "hex": "#A7B292", "role": "body text on moss panels" },
    "mossTint":    { "var": "--gs-moss-tint",   "hex": "#E4E7DA", "role": "fills, chips, hovers, empty-image blocks" },
    "amber":       { "var": "--gs-amber",       "hex": "#C68A2E", "role": "accent — rules, dots, dashed provisional borders" },
    "amberDeep":   { "var": "--gs-amber-deep",  "hex": "#7D5510", "role": "accent TEXT on cream (AA at 16px)" },
    "amberLight":  { "var": "--gs-amber-light", "hex": "#E0B156", "role": "accent on dark grounds only" },
    "umber":       { "var": "--gs-umber",       "hex": "#7C5A43", "role": "collection-only badge" },
    "umberDeep":   { "var": "--gs-umber-deep",  "hex": "#5A4030", "role": "link hover" },
    "ink":         { "var": "--gs-ink",         "hex": "#29271F", "role": "body text" },
    "muted":       { "var": "--gs-muted",       "hex": "#5F5A4C", "role": "secondary text", "note": "Darkened from #6E685B, which failed AA (4.41:1) on moss-tint — the ground used by basket panels, seasonal bands and key-fact pills. Do not lighten." },
    "rule":        { "var": "--gs-rule",        "hex": "#DDD5C4", "role": "borders" },
    "ruleSoft":    { "var": "--gs-rule-soft",   "hex": "#EDE6D6", "role": "internal dividers" },
    "disabledBg":  { "var": "--gs-disabled-bg", "hex": "#DDD5C4", "role": "disabled control ground" },
    "disabledFg":  { "var": "--gs-disabled-fg", "hex": "#8A836F", "role": "disabled control text" },
    "navy":        { "var": "--gs-navy",        "hex": "#1E2B3C", "role": "GSG Shoots — /shoots/ and the sister-brand band ONLY" },
    "navyLight":   { "var": "--gs-navy-light",  "hex": "#33455E", "role": "borders inside navy panels" },
    "navyInk":     { "var": "--gs-navy-ink",    "hex": "#9FB4D0", "role": "body text on navy" },
    "navyPaper":   { "var": "--gs-navy-paper",  "hex": "#F2F5F9", "role": "headings on navy" },
    "success":     { "var": "--gs-success",     "hex": "#4C5A3C", "role": "available state dot" },
    "successBg":   { "var": "--gs-success-bg",  "hex": "#E4E7DA", "role": "available chip ground" },
    "successFg":   { "var": "--gs-success-fg",  "hex": "#3F5231", "role": "available chip text" },
    "warning":     { "var": "--gs-warning",     "hex": "#C68A2E", "role": "limited state dot" },
    "warningBg":   { "var": "--gs-warning-bg",  "hex": "#FBF3E2", "role": "limited chip ground, match day cell" },
    "warningFg":   { "var": "--gs-warning-fg",  "hex": "#7D5510", "role": "limited chip text" },
    "error":       { "var": "--gs-error",       "hex": "#93301F", "role": "full state dot, error border", "note": "Deepened from #A0442C, which read as a darker amber when the two sat adjacent in the availability chip row." },
    "errorBg":     { "var": "--gs-error-bg",    "hex": "#F7E4DE", "role": "full chip ground, error panel" },
    "errorFg":     { "var": "--gs-error-fg",    "hex": "#7E2C1A", "role": "error text — 9.1:1 on errorBg" }
  },

  "typography": {
    "families": {
      "heading": { "var": "--gs-font-head",  "stack": "\"Cabin\", system-ui, -apple-system, sans-serif", "google": "Cabin", "weights": [400, 500, 600, 700] },
      "body":    { "var": "--gs-font-body",  "stack": "\"Cabin\", system-ui, -apple-system, sans-serif", "google": "Cabin", "weights": [400, 500, 600, 700] },
      "quote":   { "var": "--gs-font-quote", "stack": "\"Bitter\", Georgia, serif", "google": "Bitter", "weights": [400, 500, 600], "usage": "pull quotes, section eyebrows, numerals and prices only — never navigation or the logo" }
    },
    "scale": {
      "display":    { "var": "--gs-display",    "mobile": 40, "desktop": 64, "lineHeight": { "mobile": 1.04, "desktop": 1.0 }, "letterSpacing": "-0.02em", "weight": 700 },
      "h1":         { "var": "--gs-h1",         "mobile": 32, "desktop": 48, "lineHeight": 1.08, "letterSpacing": "-0.02em",  "weight": 700 },
      "h2":         { "var": "--gs-h2",         "mobile": 26, "desktop": 38, "lineHeight": 1.14, "letterSpacing": "-0.01em",  "weight": 700 },
      "h3":         { "var": "--gs-h3",         "mobile": 21, "desktop": 28, "lineHeight": 1.22, "letterSpacing": "-0.005em", "weight": 600 },
      "h4":         { "var": "--gs-h4",         "mobile": 18, "desktop": 22, "lineHeight": 1.32, "letterSpacing": "0",        "weight": 600 },
      "standfirst": { "var": "--gs-standfirst", "mobile": 18, "desktop": 22, "lineHeight": 1.5,  "weight": 400 },
      "body":       { "var": "--gs-body",       "mobile": 17, "desktop": 18, "lineHeight": 1.6,  "weight": 400 },
      "small":      { "var": "--gs-small",      "mobile": 16, "desktop": 16, "lineHeight": 1.55, "weight": 400 },
      "caption":    { "var": "--gs-caption",    "mobile": 16, "desktop": 15, "lineHeight": 1.4,  "weight": 400 },
      "button":     { "var": "--gs-button",     "mobile": 17, "desktop": 16, "weight": 600 },
      "overline":   { "var": "--gs-overline",   "mobile": 16, "desktop": 13, "letterSpacing": "0.16em", "textTransform": "uppercase", "weight": 600 }
    },
    "rules": {
      "mobileMinimum": 16,
      "mobileMinimumNote": "No text in the body ladder drops below 16px at any width. The overline is the single exception, reducing to 13px from 1024 up where it is a label rather than reading matter.",
      "measure": { "var": "--gs-measure", "value": "68ch", "note": "prose caps at 68 characters regardless of viewport" }
    }
  },

  "spacing": {
    "unit": 4,
    "scale": {
      "s1":  { "var": "--gs-s1",  "px": 4 },
      "s2":  { "var": "--gs-s2",  "px": 8 },
      "s3":  { "var": "--gs-s3",  "px": 12 },
      "s4":  { "var": "--gs-s4",  "px": 16 },
      "s5":  { "var": "--gs-s5",  "px": 24 },
      "s6":  { "var": "--gs-s6",  "px": 32 },
      "s7":  { "var": "--gs-s7",  "px": 48 },
      "s8":  { "var": "--gs-s8",  "px": 64 },
      "s9":  { "var": "--gs-s9",  "px": 96 },
      "s10": { "var": "--gs-s10", "px": 128 }
    },
    "sectionPaddingY": { "var": "--gs-section-y", "375": 48, "768": 64, "1024": 80, "1440": 96 },
    "sectionPaddingX": { "var": "--gs-margin",    "375": 20, "430": 24, "768": 32, "1024": 40, "1440": 64,
      "implementation": "clamp(20px, 5vw, 64px) — fluid, not stepped. A fixed 64px consumed 34% of a 375px viewport and caused grid overflow." }
  },

  "layout": {
    "maxContentWidth": { "var": "--gs-max", "px": 1280 },
    "touchTarget":     { "var": "--gs-tap", "px": 44, "note": "Binding on all controls — buttons, basket, steppers, calendar cells, icon buttons. Not applied to inline text links in navigation." },
    "breakpoints": [
      { "name": "Small phone", "width": 375,  "columns": 4,  "gutter": 16, "margin": 20 },
      { "name": "Phone",       "width": 430,  "columns": 4,  "gutter": 16, "margin": 24 },
      { "name": "Tablet",      "width": 768,  "columns": 8,  "gutter": 24, "margin": 32 },
      { "name": "Laptop",      "width": 1024, "columns": 12, "gutter": 24, "margin": 40 },
      { "name": "Desktop",     "width": 1440, "columns": 12, "gutter": 32, "margin": 64 }
    ],
    "gridRule": "Every minmax() floor is wrapped in min(<len>, 100%) so no track can refuse to shrink. A bare minmax floor is the single most common cause of horizontal overflow in this system."
  },

  "radii": {
    "r0":    { "var": "--gs-r0",    "px": 0,   "usage": "full-bleed media" },
    "r1":    { "var": "--gs-r1",    "px": 4,   "usage": "chips, key-fact pills" },
    "r2":    { "var": "--gs-r2",    "px": 8,   "usage": "inputs, calendar cells" },
    "r3":    { "var": "--gs-r3",    "px": 12,  "usage": "contained photography, inner panels" },
    "r4":    { "var": "--gs-r4",    "px": 20,  "usage": "cards, bands" },
    "rFull": { "var": "--gs-rfull", "px": 999, "usage": "buttons, steppers, availability chips" }
  },

  "elevation": {
    "e0": { "var": "--gs-e0", "value": "none", "usage": "flat" },
    "e1": { "var": "--gs-e1", "value": "0 1px 2px rgba(41,39,31,.06)", "usage": "resting card" },
    "e2": { "var": "--gs-e2", "value": "0 4px 14px -4px rgba(41,39,31,.14)", "usage": "card hover" },
    "e3": { "var": "--gs-e3", "value": "0 18px 40px -16px rgba(41,39,31,.26)", "usage": "drawer, sticky header, lightbox" }
  },

  "motion": {
    "hover": { "var": "--gs-dur-hover", "ms": 120, "easing": "var(--gs-ease)" },
    "panel": { "var": "--gs-dur-panel", "ms": 240, "easing": "var(--gs-ease-out)" },
    "page":  { "var": "--gs-dur-page",  "ms": 180, "easing": "var(--gs-ease)" },
    "easing": {
      "ease":    { "var": "--gs-ease",     "value": "cubic-bezier(.2,0,.38,1)" },
      "easeOut": { "var": "--gs-ease-out", "value": "cubic-bezier(.2,0,0,1)" }
    },
    "reducedMotion": "All three durations resolve to 0ms under prefers-reduced-motion: reduce. Nothing bounces, overshoots or springs anywhere in the system."
  },

  "focus": {
    "onCream":       { "var": "--gs-focus",       "value": "0 0 0 2px #EFE9DB, 0 0 0 5px #7D5510" },
    "onDark":        { "var": "--gs-focus-dark",  "value": "0 0 0 2px #36422E, 0 0 0 5px #E0B156" },
    "onPhotography": { "var": "--gs-focus-photo", "value": "0 0 0 2px #FFFFFF, 0 0 0 5px #29271F" },
    "rule": "A 2px ground-coloured gap then a 3px ring. The gap is what makes it survive over photography, where a bare ring disappears into image detail. One treatment everywhere; never removed without replacement."
  },

  "photography": {
    "ratios": {
      "hero":    { "var": "--gs-ratio-hero",    "value": "16 / 9" },
      "card":    { "var": "--gs-ratio-card",    "value": "4 / 3" },
      "product": { "var": "--gs-ratio-product", "value": "1 / 1" },
      "gallery": { "var": "--gs-ratio-gallery", "value": "3 / 2" }
    },
    "radius": { "var": "--gs-photo-radius", "px": 12, "note": "0 when full-bleed" },
    "scrim":  { "var": "--gs-scrim", "value": "linear-gradient(to top, rgba(41,39,31,.84) 0%, rgba(41,39,31,.78) 62%, rgba(41,39,31,.44) 86%, rgba(41,39,31,0) 100%)" },
    "scrimBase": { "var": "--gs-scrim-base", "value": "rgba(41,39,31,.08)", "note": "flat tint under the gradient, evens out pale skies" },
    "rules": [
      "Text over photography is ALWAYS cream (#F7F2E8). Amber letterforms on sky sit on the AA threshold and flip either side of it as the scrim changes — amber appears as a short rule beside the eyebrow instead.",
      "No filters, duotones or washes. The photography is the farm; it is not treated.",
      "Empty-image treatment is a moss-tint (#E4E7DA) block carrying the relevant line mark — never a broken frame or a grey box."
    ],
    "checkedPairings": {
      "note": "Verified contrast ratios for the pairings that recur across templates. Anything below 4.5 is disallowed for normal text.",
      "muted on moss-tint": 5.19,
      "muted on cream": 5.38,
      "muted on surface": 6.14,
      "muted on white": 6.51,
      "amberDeep on cream": 5.02,
      "errorFg on errorBg": 9.1,
      "disallowed": [
        "#6E685B on #E4E7DA (4.41) — the reason --gs-muted was darkened",
        "amber #C68A2E letterforms over photography — sits on the threshold and flips as the scrim changes"
      ]
    }
  },

  "provisional": {
    "class": "gs-provisional",
    "style": { "color": "var(--gs-muted)", "borderBottom": "1px dashed var(--gs-amber)", "fontStyle": "italic" },
    "note": "Any value not confirmed by the farm carries this treatment so it cannot be mistaken for approved content. Currently applied to: all shop prices, delivery radius, phone number, and the cottage differentiator (see confirmed.cottageDifferentiator \u2014 The Hayloft's is now sourced and can lose the provisional treatment; The Dutch Barn's remains open).",
    "values": {
      "price": "£ — —",
      "deliveryRadius": "(pending)",
      "phone": "(pending)"
    }
  },

  "confirmed": {
    "note": "Facts confirmed by the farm and safe to typeset as final.",
    "address": "Radwinter Road, Ashdon, Saffron Walden, CB10 2LZ",
    "cottages": ["The Dutch Barn", "The Hayloft"],
    "cottageFacts": ["Sleeps 10", "5 bedrooms", "Private hot tub", "Level access"],
    "cottageDifferentiator": {
      "theHayloft": "Ground-floor wet room",
      "theDutchBarn": null,
      "source": "Cottages.com property listing",
      "note": "Supersedes the photograph-inferred 'freestanding bath vs walk-in shower room' still present in the exported HTML. Sourced, and the better differentiator on the merits \u2014 for a group of ten it is an accessibility fact. The Dutch Barn's counterpart feature is unconfirmed; if none exists, the slot can carry 'Ask us which suits your group'."
    },
    "fishing": {
      "dayTicketAdult": "£10",
      "dayTicketUnder16": "£5",
      "hours": "Dawn till dusk",
      "pegs": 23,
      "lakes": 2,
      "matchPricePerLake": "£100",
      "pegSplitBetweenLakes": null,
      "pegSplitNote": "Deliberately unstated — the split of 23 pegs across the two lakes is not confirmed."
    },
    "firewood": {
      "type": "Mixed seasoned hardwood, predominantly ash and oak",
      "formats": [
        "Cubic metre bag",
        "Loose, 1 cubic metre",
        "Loose, 1.3 cubic metre",
        "Netted bags (size to be confirmed)"
      ],
      "softwood": "Not currently stocked. Can be sourced on request — do not create a product for it.",
      "prices": null,
      "readyToBurn": {
        "status": "UNRESOLVED — BLOCKS SHOP LAUNCH",
        "issue": "Under the Air Quality (Domestic Solid Fuels Standards) (England) Regulations 2020, wood fuel sold in volumes under 2 cubic metres must be certified Ready to Burn (moisture content 20% or less) and display the logo, supplier name and certification number. Every format listed above is under 2m\u00b3, so all of them fall in scope.",
        "needed": "Woodsure certification number from the farm, or confirmation that certification is in progress.",
        "note": "Not legal advice. Worth confirming with Uttlesford trading standards."
      },
      "unitClarityNote": "A cubic metre bag holds a cubic metre of bag, not of solid timber \u2014 roughly half that once air between logs is accounted for. State this plainly on the product page; it is the single sentence that prevents the most phone calls."
    }
  }
}
