/* ==========================================================================
   MAHER KIT — the only custom CSS in the system.
   Everything DaisyUI + Tailwind already provide is used as-is. This file adds
   what they do not: type roles, tactile surfaces, seam details, and legible
   text on soft components.

   Colours, fonts, type steps, texture strength and pitch all come from
   maher.css tokens. What stays here is each role's recipe (which step, what
   weight and leading) and the geometry inside each fabric pattern.

   Type: every role size is a step from the theme's modular scale multiplied by
   --type-scale, which can be set on any container (a deck root uses 1.5).

   Layering: everything sits in a "maher" sublayer inside Tailwind's
   utilities layer, loaded after daisyUI. That order means
     daisyUI components  <  maher classes  <  Tailwind utilities
   so `class="t-title text-sm"` works the way an author expects.
   ========================================================================== */

@layer base {
  body {
    font-family: var(--font-body);
    background-color: var(--color-base-100);
    color: var(--color-base-content);
    -webkit-font-smoothing: antialiased;
  }
}

@layer utilities {
  @layer maher {

    /* --- Type roles ------------------------------------------------------
       A modular scale (ratio 1.3) with a 15px floor for anything a reader relies on;
       roles map to a small set of steps. Headings balance their lines so no title ends
       on a single word; running text uses pretty wrapping.                    */
    .t-display,
    .t-title,
    .t-heading,
    .t-figure, .t-figure-sm, .t-figure-md, .t-figure-lg {
      font-family: var(--font-display);
      font-optical-sizing: auto;
      font-variation-settings: var(--_fvs, var(--font-display-settings));
    }
    :is(h1, h2, h3, h4, .t-display, .t-title, .t-heading, .t-subhead, .t-lead, .t-caption, .stat-desc, figcaption, blockquote p) { text-wrap: balance; }
    :is(.flow, .t-body, .measure) :is(p, li), p.t-body, p.measure { text-wrap: pretty; }

    .t-display { font-weight: 400; line-height: 1.02; letter-spacing: -0.022em;
                 font-size: calc(clamp(var(--step-4), calc(var(--step-2) + 3vw), var(--step-6)) * var(--type-scale)); }
    .t-title   { font-weight: 400; line-height: 1.1; letter-spacing: -0.015em;
                 font-size: calc(clamp(var(--step-2), calc(var(--step-1) + 1.2vw), var(--step-3)) * var(--type-scale)); }
    .t-heading { font-weight: 500; line-height: 1.2; letter-spacing: -0.01em; font-size: calc(var(--step-1) * var(--type-scale)); }
    .t-lead    { font-weight: 400; line-height: 1.5; font-size: calc(var(--step-half) * var(--type-scale)); }
    .t-subhead { font-weight: 600; line-height: 1.35; font-size: calc(var(--step-half) * var(--type-scale)); }
    .t-body    { line-height: 1.6; font-size: calc(var(--step-0) * var(--type-scale)); }
    .t-caption { line-height: 1.45; font-size: max(var(--type-floor), calc(var(--step--1) * var(--type-scale))); color: var(--_quiet, var(--color-muted)); }
    /* Eyebrow: quiet ink by default; add text-accent-ink for the one cognac eyebrow a view is allowed.
       Capitals take 10% tracking (Butterick: 5 to 12%). */
    .t-eyebrow { font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase; line-height: 1.4;
                 font-size: max(0.875rem, calc(var(--step--1) * 0.93 * var(--type-scale))); color: var(--_quiet, var(--color-muted)); }

    /* Voice: Fraunces at full character (high softness, wonky forms), for ONE line per view:
       a cover title, a deck statement, a one-pager headline, a quote. 32px and up only. */
    .t-voice { --_fvs: var(--font-display-expressive); font-family: var(--font-display); font-variation-settings: var(--font-display-expressive); font-optical-sizing: auto; }
    em.t-voice, .t-voice em, .t-italic { font-style: italic; }

    /* Figures: big numbers, four sizes, all follow --type-scale. Fraunces has proportional
       figures only; for aligned columns of numbers use .num instead. */
    .t-figure, .t-figure-sm, .t-figure-md, .t-figure-lg { font-weight: 400; line-height: 1; letter-spacing: -0.02em; }
    .t-figure-sm { font-size: calc(var(--step-2) * var(--type-scale)); }
    .t-figure-md { font-size: calc(var(--step-3) * var(--type-scale)); }
    .t-figure    { font-size: calc(var(--step-4) * var(--type-scale)); }
    .t-figure-lg { font-size: calc(var(--step-5) * var(--type-scale)); }
    /* Numeral: the giant chapter number on section dividers. */
    .t-numeral { font-family: var(--font-display); font-weight: 300; line-height: 0.85; letter-spacing: -0.04em; font-size: calc(var(--step-6) * 1.6 * var(--type-scale)); font-variation-settings: var(--font-display-expressive); }
    /* Hero figure: the one number a big-number slide exists for (about 240px on the 1280 canvas).
       The unit sits in a .unit span at 40%, so the figure stays the thing you see. */
    .t-hero { font-family: var(--font-display); font-weight: 350; line-height: 0.85; letter-spacing: -0.035em; font-size: calc(var(--step-6) * 2.1 * var(--type-scale)); font-variant-numeric: lining-nums; }
    .t-hero .unit { font-size: 0.4em; letter-spacing: 0; margin-inline-start: 0.08em; }
    .t-mono    { font-family: var(--font-mono); font-size: calc(var(--step--1) * var(--type-scale)); }
    /* Label: quiet, sentence-case. Keys in key-value lists, column heads, stat titles. */
    .t-label   { font-weight: 600; line-height: 1.3; font-size: max(var(--type-floor), calc(var(--step--1) * var(--type-scale))); color: var(--_quiet, var(--color-muted)); }
    /* Numbers in columns: Instrument Sans tabular figures, aligned right, never broken.
       Money and quantities use this; Plex Mono is only for codes and identifiers. */
    .num { font-family: var(--font-body); font-variant-numeric: tabular-nums; text-align: end; white-space: nowrap; }
    .stat-desc { font-size: max(var(--type-floor), calc(var(--step--1) * var(--type-scale))); line-height: 1.4; }
    .stat-title { font-size: max(var(--type-floor), calc(var(--step--1) * var(--type-scale))); }

    /* Dates and phone numbers never break across lines. Wrap dates in <time>. */
    time, a[href^="tel:"] { white-space: nowrap; }

    /* Reading floor: lists and tables people read on paper or a phone stay at 15px or more. */
    :where(.list, .table, .card-body, .alert, .fieldset-legend, .fieldset .label, .table thead, .table tfoot) { font-size: max(var(--type-floor), 0.875rem); }
    /* Small controls and status words are client-facing text too: 15px, never the 12px default.
       (btn-xs and badge-xs stay small for dense library chrome; do not use them in artifacts.) */
    .btn-sm, .badge:not(.badge-xs) { font-size: var(--type-floor); }
    /* The scale runs upward: small 15px, default 16px, large as daisyUI sets it. */
    .btn:not(.btn-xs, .btn-sm, .btn-lg, .btn-xl) { font-size: 1rem; }
    .badge-sm { --size: calc(var(--size-selector, 0.25rem) * 5.5); }
    /* Fields at 16px: legible, and phones do not zoom the page when one takes focus. */
    :is(.input, .select, .textarea):not(.input-xs, .input-sm, .select-xs, .select-sm) { font-size: 1rem; }
    /* daisyUI's built-in 60% grey fails contrast; quiet text in components uses the kit's muted ink. */
    :where(.stat-desc, .stat-title, .table thead, .table tfoot, .fieldset .label, .tabs .tab:not(.tab-active)) { color: var(--_quiet, var(--color-muted)); }
    .table tfoot { color: var(--color-base-content); }
    :where(p, li, dd):not([class*="t-"]) { text-wrap: pretty; }

    /* Accounting totals: one rule above, a double rule below. */
    tfoot :is(th, td) { border-top: var(--border) solid var(--color-base-content); border-bottom: 3px double var(--color-base-content); }
    /* Column heads carry a camel underline: the plaid's stripe doing a job. */
    .table thead tr { border-bottom: 2px solid var(--color-camel); }
    /* Key-value rows align on the baseline, so labels and values sit on one line of type. */
    dl :is(div, .grid):has(> dt) { align-items: baseline; }

    /* On coloured surfaces, quiet text (captions, labels, eyebrows, stat titles) takes the
       surface's content colour: at 90% on navy, slate and leather, where that still clears
       4.5:1 (checked by the token build), and at full strength on every lighter fill.
       Base surfaces nested inside a fill reset it to muted. */
    .bg-neutral   { --_quiet: color-mix(in oklab, var(--color-neutral-content) 90%, transparent); }
    .bg-secondary { --_quiet: color-mix(in oklab, var(--color-secondary-content) 90%, transparent); }
    .bg-leather   { --_quiet: color-mix(in oklab, var(--color-leather-content) 90%, transparent); }
    .bg-primary { --_quiet: var(--color-primary-content); }
    .bg-accent  { --_quiet: var(--color-accent-content); }
    .bg-info    { --_quiet: var(--color-info-content); }
    .bg-success { --_quiet: var(--color-success-content); }
    .bg-warning { --_quiet: var(--color-warning-content); }
    .bg-error   { --_quiet: var(--color-error-content); }
    :is(.bg-base-100, .bg-base-200, .bg-base-300) { --_quiet: initial; }
    /* Large slate surfaces (divider slides, pinstripe bands) use the band slate, which is
       deeper than the secondary fill at night. Redefining the pair here keeps bg-secondary
       and text-secondary-content working unchanged in markup. */
    .bg-secondary:is(.slide, .tx-pinstripe) { --color-secondary: var(--color-band-secondary); --color-secondary-content: var(--color-band-secondary-content); }

    /* Quotation: display italic, opening mark hung outside the text edge. */
    .quote { display: flex; flex-direction: column; gap: 0.75rem; }
    .quote::before { content: ""; display: block; width: 2.5rem; height: 3px; border-radius: 999px; background-color: var(--color-wood); }
    .quote blockquote p { font-family: var(--font-display); font-style: italic; font-variation-settings: var(--font-display-expressive);
                          font-size: calc(var(--step-2) * var(--type-scale)); line-height: 1.25; letter-spacing: -0.01em; hanging-punctuation: first; }
    @supports not (hanging-punctuation: first) { .quote blockquote p { text-indent: -0.42em; } }
    .quote figcaption { font-size: calc(var(--step--1) * var(--type-scale)); color: var(--color-muted); }
    .quote figcaption strong { color: var(--color-base-content); font-weight: 600; }

    /* Marker: a banana highlight swash behind the ONE phrase or number a reader must
       remember on the page. One per view. */
    .marker {
      --_swash: color-mix(in oklab, var(--color-highlight) var(--marker-strength, 70%), transparent);
      color: var(--color-base-content);
      background-image: linear-gradient(transparent 58%, var(--_swash) 58%, var(--_swash) 92%, transparent 92%);
      box-decoration-break: clone; -webkit-box-decoration-break: clone; padding-inline: 0.12em; margin-inline: -0.12em;
    }

    /* Title rule: the cherry-wood inlay under a document or section title. */
    .rule-title { display: block; width: 3rem; height: 3px; border-radius: 999px; background-color: var(--color-wood); margin-block-start: 0.75rem; }

    /* Selvedge: a narrow plaid edge, the way the pillow's weave finishes at its seam.
       Drawn as background layers, so it combines with any fabric (their ::before) and the seam (::after). */
    .selvedge {
      background-image:
        linear-gradient(0deg, transparent 40%, var(--color-camel) 40% 46%, transparent 46% 54%, var(--color-neutral) 54% 66%, transparent 66% 74%, var(--color-camel) 74% 80%, transparent 80%),
        linear-gradient(var(--color-base-300), var(--color-base-300));
      background-size: 8px 36px, 8px 100%;
      background-repeat: repeat-y, no-repeat;
      background-position: 0 0, 0 0;
    }

    /* --- Soft components: text-grade ink instead of the fill colour ------- */
    .badge-soft.badge-primary, .btn-soft.btn-primary { color: var(--color-primary-ink); }
    .btn-soft.btn-secondary { color: var(--color-secondary-ink); }
    .btn-soft.btn-accent { color: var(--color-accent-ink); }
    .badge-soft.badge-secondary { color: var(--color-secondary-ink); }
    .badge-soft.badge-accent    { color: var(--color-accent-ink); }
    .badge-soft.badge-info,    .alert-soft.alert-info    { color: var(--color-info-ink); }
    .badge-soft.badge-success, .alert-soft.alert-success { color: var(--color-success-ink); }
    .badge-soft.badge-warning, .alert-soft.alert-warning { color: var(--color-warning-ink); }
    .badge-soft.badge-error,   .alert-soft.alert-error   { color: var(--color-error-ink); }

    /* --- Tactile surfaces -------------------------------------------------
       Fabrics behave like upholstery, not wallpaper: patterns are finished at
       their edges and never cross small text. One textured surface per section.
       Strength scales with --texture (0 to 1).                                 */

    /* Linen: an irregular weave (fine warp and weft plus occasional slubs), so it
       reads as cloth rather than graph paper in both modes. */
    .tx-linen { position: relative; isolation: isolate; }
    .tx-linen::before {
      content: "";
      position: absolute;
      inset: 0;
      z-index: -1;
      border-radius: inherit;
      pointer-events: none;
      /* Woven fibre: two stretched noise fields (warp and weft) used as a mask over
         the ink colour. Irregular like cloth; no grid in either mode. */
      background-color: var(--color-base-content);
      opacity: calc(0.16 * var(--texture) * var(--linen-strength, 1));
      -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cfilter id='w' x='0' y='0' width='100%25' height='100%25'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.75 0.028' numOctaves='2' seed='4' stitchTiles='stitch' result='warp'/%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.028 0.75' numOctaves='2' seed='9' stitchTiles='stitch' result='weft'/%3E%3CfeComposite in='warp' in2='weft' operator='arithmetic' k1='0' k2='0.55' k3='0.55' k4='0' result='cloth'/%3E%3CfeColorMatrix in='cloth' type='matrix' values='0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  2.6 0 0 0 -1.05'/%3E%3C/filter%3E%3Crect width='240' height='240' filter='url%28%23w%29'/%3E%3C/svg%3E");
      mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cfilter id='w' x='0' y='0' width='100%25' height='100%25'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.75 0.028' numOctaves='2' seed='4' stitchTiles='stitch' result='warp'/%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.028 0.75' numOctaves='2' seed='9' stitchTiles='stitch' result='weft'/%3E%3CfeComposite in='warp' in2='weft' operator='arithmetic' k1='0' k2='0.55' k3='0.55' k4='0' result='cloth'/%3E%3CfeColorMatrix in='cloth' type='matrix' values='0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  2.6 0 0 0 -1.05'/%3E%3C/filter%3E%3Crect width='240' height='240' filter='url%28%23w%29'/%3E%3C/svg%3E");
      -webkit-mask-size: 240px 240px;
      mask-size: 240px 240px;
    }

    /* Pinstripe: slate sofa. The stripes take the surface's text colour and live in one
       field, the upper right of the band; the rest is plain cloth, so words sit clear of
       stripes. Keep text to the left or the lower part of a band. Variants: .stripe-center
       (navbars: field in the empty middle), .stripe-middle (covers with text in the top and
       bottom corners), .stripe-top (centred or full-width content), .stripe-edge (small cards
       and tiles), .stripe-left (text on the right). On phones every band keeps only a selvedge. */
    .tx-pinstripe { position: relative; isolation: isolate; }
    .tx-pinstripe::before {
      content: "";
      position: absolute;
      inset: 0;
      z-index: -1;
      border-radius: inherit;
      pointer-events: none;
      background-image: repeating-linear-gradient(
        90deg,
        transparent 0 calc(var(--texture-pitch) - 1px),
        color-mix(in oklab, currentColor calc(24% * var(--texture)), transparent) calc(var(--texture-pitch) - 1px) var(--texture-pitch)
      );
      /* The field: the upper right of the band (right of 62%, upper half), fading softly. */
      -webkit-mask-image: linear-gradient(90deg, transparent 0 62%, black 82%), linear-gradient(180deg, black 0 22%, transparent 45%);
      mask-image: linear-gradient(90deg, transparent 0 62%, black 82%), linear-gradient(180deg, black 0 22%, transparent 45%);
      -webkit-mask-composite: source-in;
      mask-composite: intersect;
    }
    /* Bars with content at both ends (navbars): the field sits in the empty middle.
       Move it with --stripe-from / --stripe-to (defaults 26% and 66%). */
    .tx-pinstripe.stripe-center::before {
      -webkit-mask-image: linear-gradient(90deg, transparent 0 var(--stripe-from, 26%), black calc(var(--stripe-from, 26%) + 10%) calc(var(--stripe-to, 66%) - 10%), transparent var(--stripe-to, 66%));
      mask-image: linear-gradient(90deg, transparent 0 var(--stripe-from, 26%), black calc(var(--stripe-from, 26%) + 10%) calc(var(--stripe-to, 66%) - 10%), transparent var(--stripe-to, 66%));
    }
    /* Covers with text in the top and bottom corners: the field sits in the middle right. */
    .tx-pinstripe.stripe-middle::before {
      -webkit-mask-image: linear-gradient(90deg, transparent 0 55%, black 78%), linear-gradient(180deg, transparent 0 18%, black 26% 36%, transparent 44%);
      mask-image: linear-gradient(90deg, transparent 0 55%, black 78%), linear-gradient(180deg, transparent 0 18%, black 26% 36%, transparent 44%);
    }
    /* Small cards and tiles, full of words: a narrow selvedge of stripes at the right edge. */
    .tx-pinstripe.stripe-edge::before {
      -webkit-mask-image: linear-gradient(90deg, transparent 0 80%, black 94%);
      mask-image: linear-gradient(90deg, transparent 0 80%, black 94%);
    }
    /* Content centred or running full width: the field is a strip along the top edge. */
    .tx-pinstripe.stripe-top::before {
      -webkit-mask-image: linear-gradient(180deg, black 0 var(--stripe-depth, 9%), transparent calc(var(--stripe-depth, 9%) + 9%));
      mask-image: linear-gradient(180deg, black 0 var(--stripe-depth, 9%), transparent calc(var(--stripe-depth, 9%) + 9%));
    }
    /* Narrow bands (under 40rem wide, e.g. on phones): text fills the band, so the stripes
       retreat into the right-hand padding, a selvedge. The band is its own size container.
       Slides keep their field: they are a fixed canvas, scaled. */
    .tx-pinstripe:not(.slide, span) { container-type: inline-size; }
    @container (max-width: 40rem) {
      .tx-pinstripe:not(.slide)::before, .tx-pinstripe:not(.slide)[class*="stripe-"]::before {
        -webkit-mask-image: linear-gradient(90deg, transparent calc(100% - 0.9rem), black calc(100% - 0.35rem));
        mask-image: linear-gradient(90deg, transparent calc(100% - 0.9rem), black calc(100% - 0.35rem));
      }
    }
    /* Text on the right (a closing slide beside a portrait): the field moves to the upper left. */
    .tx-pinstripe.stripe-left::before {
      -webkit-mask-image: linear-gradient(270deg, transparent 0 55%, black 78%), linear-gradient(180deg, black 0 30%, transparent 60%);
      mask-image: linear-gradient(270deg, transparent 0 55%, black 78%), linear-gradient(180deg, black 0 30%, transparent 60%);
    }

    /* Plaid: throw pillow. Camel and navy ink stripes over a soft check, repeated
       in whole tiles only (round sizing from the top-left corner). Backdrop or selvedge edge; never put text on it. */
    .tx-plaid {
      --_camel: color-mix(in oklab, var(--color-camel) calc(60% * var(--texture)), transparent);
      --_ink: color-mix(in oklab, var(--color-neutral) calc(55% * var(--texture)), transparent);
      --_check: color-mix(in oklab, var(--color-base-300) calc(60% * var(--texture)), transparent);
      background-size: 112px 112px;
      background-repeat: round;
      background-position: 0 0;
      background-image:
        linear-gradient(90deg, transparent 42%, var(--_camel) 42% 44%, transparent 44% 47%, var(--_ink) 47% 53%, transparent 53% 56%, var(--_camel) 56% 58%, transparent 58%),
        linear-gradient(0deg, transparent 42%, var(--_camel) 42% 44%, transparent 44% 47%, var(--_ink) 47% 53%, transparent 53% 56%, var(--_camel) 56% 58%, transparent 58%),
        linear-gradient(90deg, var(--_check) 50%, transparent 50%),
        linear-gradient(0deg, var(--_check) 50%, transparent 50%);
    }

    /* Tufted: leather ottoman. Buttons sit on a whole-tile lattice set in from the
       stitched welt, so no button is ever cut by an edge. For bg-leather surfaces. */
    .tx-tufted { position: relative; isolation: isolate; --_tuft: 56px; }
    .tx-tufted::before {
      content: "";
      position: absolute;
      inset: calc(var(--stitch-inset) + 10px);
      padding: 4px;
      margin: -4px;
      z-index: -1;
      pointer-events: none;
      --_seam: color-mix(in oklab, var(--color-seam) calc(20% * var(--texture)), transparent);
      --_button: color-mix(in oklab, var(--color-seam) calc(42% * var(--texture)), transparent);
      background-origin: content-box;
      background-clip: border-box;
      background-repeat: round;
      background-size: var(--_tuft) var(--_tuft);
      background-image:
        radial-gradient(circle at 0 0, var(--_button) 0 2.5px, transparent 3.5px),
        radial-gradient(circle at 100% 0, var(--_button) 0 2.5px, transparent 3.5px),
        radial-gradient(circle at 0 100%, var(--_button) 0 2.5px, transparent 3.5px),
        radial-gradient(circle at 100% 100%, var(--_button) 0 2.5px, transparent 3.5px),
        radial-gradient(circle at 50% 50%, var(--_button) 0 2.5px, transparent 3.5px),
        linear-gradient(45deg, transparent calc(50% - 0.6px), var(--_seam) calc(50% - 0.6px) calc(50% + 0.6px), transparent calc(50% + 0.6px)),
        linear-gradient(-45deg, transparent calc(50% - 0.6px), var(--_seam) calc(50% - 0.6px) calc(50% + 0.6px), transparent calc(50% + 0.6px));
    }

    /* The seam: Maher's signature detail. A stitched line inside a nickel border,
       meeting a short cherry-wood welt at the top-left corner. One per view, on
       the featured element (a cover, the "In brief" box, a title slide). */
    .seam { position: relative; border: var(--border) solid var(--color-nickel); }
    .seam::after {
      content: "";
      position: absolute;
      inset: var(--stitch-inset);
      border: 1px dashed color-mix(in oklab, currentColor calc(34% * var(--texture) + 10%), transparent);
      border-radius: max(0px, calc(var(--radius-box) - 4px));
      pointer-events: none;
      background-image: linear-gradient(var(--color-wood), var(--color-wood)), linear-gradient(var(--color-wood), var(--color-wood));
      background-size: 2.25rem 2px, 2px 2.25rem;
      background-position: -1px -1px, -1px -1px;
      background-repeat: no-repeat;
      background-origin: border-box;
    }

    /* Material strip: the room in one line. Linen, plaid, pinstripe, and leather
       with one button, left to right. Cover edges, slide progress, headers. */
    .material-strip {
      display: block;
      block-size: 8px;
      border-radius: 2px;
      background-repeat: no-repeat;
      background-size: 100% 100%, 25% 100%, 25% 100%, 25% 100%, 100% 100%;
      background-position: 0 0, 33.333% 0, 66.667% 0, 0 0, 0 0;
      background-image:
        radial-gradient(circle at 87.5% 50%, color-mix(in oklab, var(--color-seam) 60%, transparent) 0 1.5px, transparent 2.2px),
        repeating-linear-gradient(90deg, transparent 0 18px, var(--color-camel) 18px 20px, transparent 20px 24px, var(--color-neutral) 24px 30px, transparent 30px 34px, var(--color-camel) 34px 36px, transparent 36px 56px),
        repeating-linear-gradient(90deg, var(--color-secondary) 0 13px, color-mix(in oklab, var(--color-secondary-content) 45%, var(--color-secondary)) 13px 14px),
        repeating-linear-gradient(0deg, color-mix(in oklab, var(--color-base-content) 12%, var(--color-base-300)) 0 1px, var(--color-base-300) 1px 3px),
        linear-gradient(90deg, var(--color-base-300) 0 25%, var(--color-base-200) 25% 50%, var(--color-secondary) 50% 75%, var(--color-leather) 75% 100%);
    }
    .material-strip[style*="--progress"] { clip-path: inset(0 calc(100% - var(--progress) * 100%) 0 0); }

    /* --- Icons -------------------------------------------------------------
       Lucide outlines (theme/icons/), inline, stroke currentColor. Always next to
       a word, or inside a button with an aria-label (R20). */
    .icon { inline-size: 1.15em; block-size: 1.15em; flex: none; vertical-align: -0.2em; stroke-width: 1.6; }
    .icon-lg { inline-size: 2rem; block-size: 2rem; stroke-width: 1.3; }
    .icon-chip { display: inline-grid; place-items: center; inline-size: 2.25rem; block-size: 2.25rem; flex: none; border-radius: 999px;
                 background-color: var(--color-base-200); border: var(--border) solid var(--color-base-300); color: var(--color-base-content); }
    .icon-chip .icon { inline-size: 1.1rem; block-size: 1.1rem; }

    /* --- Photography ---------------------------------------------------------
       One warm, matte grade on every photo; named crops only; never text on a
       photo; no stock. When the file is missing the frame shows linen and the
       monogram instead (R21). */
    .photo { position: relative; isolation: isolate; overflow: hidden; border-radius: var(--radius-box); border: var(--border) solid var(--color-base-300); background-color: var(--color-base-200); }
    .photo > img { position: relative; display: block; inline-size: 100%; block-size: 100%; object-fit: cover; object-position: var(--focus, 50% 50%); filter: var(--photo-grade); }
    .photo-portrait { aspect-ratio: 4 / 5; }
    .photo-wide { aspect-ratio: 3 / 2; }
    .photo-square { aspect-ratio: 1 / 1; }
    /* Mat: the photo sits inside a border of page colour, wide enough that a stitch line runs clear of it. */
    .photo-mat { padding: calc(var(--stitch-inset) + 7px); background-color: var(--color-base-100); }
    .photo-mat > img { border-radius: max(0px, calc(var(--radius-box) - 6px)); }
    /* The fallback is always there, underneath: monogram on linen. The photo covers it the
       moment it paints, so a slow or lazy image never shows an empty frame, and a missing
       one (shell.js adds .photo-empty on error) simply leaves the fallback showing. */
    .photo > .photo-fallback { display: grid; place-items: center; position: absolute; inset: 0; z-index: -1; color: var(--color-muted); }
    .photo > .photo-fallback .mark { block-size: 28%; }
    .photo-empty > img, .photo > img[data-maher-missing] { display: none; }  /* the bundler marks missing photos, so previews without JavaScript show the fallback too */
    .photo::before {
      content: ""; position: absolute; inset: 0; z-index: -2; opacity: calc(0.6 * var(--texture) * var(--linen-strength, 1)); mix-blend-mode: var(--texture-blend);
      background-image:
        repeating-linear-gradient(0deg, color-mix(in oklab, var(--color-base-content) 4.5%, transparent) 0 1px, transparent 1px 3px),
        repeating-linear-gradient(0deg, transparent 0 11px, color-mix(in oklab, var(--color-base-content) 5%, transparent) 11px 12.5px, transparent 12.5px 31px);
    }
    /* Material fallbacks: a photo that stands for a material falls back to that material,
       so a contact sheet without its photographs still reads as five different things. */
    .photo[data-material] > .photo-fallback .mark { display: none; }
    /* A slot waiting for someone else's picture says so, in quiet type, instead of a blank frame. */
    .photo > .photo-fallback-label { font-size: var(--type-floor); color: var(--color-muted); text-align: center; padding: 1rem; }
    /* The chair tile keeps its plaid pillow: a square of camel and navy check in the lower right. */
    .photo[data-material="linen"]:not(:has(.photo-fallback-label))::after {
      content: ""; position: absolute; z-index: -1; inset: auto 14% 16% auto; inline-size: 34%; aspect-ratio: 1; border-radius: 10%;
      background-color: var(--color-base-100);
      background-image:
        repeating-linear-gradient(90deg, transparent 0 22%, color-mix(in oklab, var(--color-camel) 70%, transparent) 22% 30%, transparent 30% 48%, color-mix(in oklab, var(--color-neutral) 55%, transparent) 48% 52%, transparent 52% 100%),
        repeating-linear-gradient(0deg, transparent 0 22%, color-mix(in oklab, var(--color-camel) 55%, transparent) 22% 30%, transparent 30% 48%, color-mix(in oklab, var(--color-neutral) 45%, transparent) 48% 52%, transparent 52% 100%);
      background-size: 50% 50%; box-shadow: var(--shadow-raised);
    }
    .photo[data-material="pinstripe"] { background-color: var(--color-band-secondary); }
    .photo[data-material="pinstripe"]::before { opacity: 1; mix-blend-mode: normal; background-image: repeating-linear-gradient(90deg, transparent 0 calc(var(--texture-pitch) - 1px), color-mix(in oklab, var(--color-band-secondary-content) 30%, transparent) calc(var(--texture-pitch) - 1px) var(--texture-pitch)); }
    .photo[data-material="sage"] { background-color: var(--color-primary); }
    .photo[data-material="sage"]::before { opacity: 1; mix-blend-mode: normal; background-image: linear-gradient(90deg, transparent 0 14%, color-mix(in oklab, var(--color-primary-content) 22%, transparent) 14% 15%, transparent 15% 85%, color-mix(in oklab, var(--color-primary-content) 22%, transparent) 85% 86%, transparent 86%), linear-gradient(180deg, transparent 0 14%, color-mix(in oklab, var(--color-primary-content) 22%, transparent) 14% 15%, transparent 15% 85%, color-mix(in oklab, var(--color-primary-content) 22%, transparent) 85% 86%, transparent 86%); }
    .photo[data-material="wood"] { background-color: var(--color-wood); }
    .photo[data-material="wood"]::before { opacity: 0.5; mix-blend-mode: normal; background-image: repeating-radial-gradient(ellipse 140% 40% at 30% 120%, transparent 0 9px, color-mix(in oklab, black 18%, transparent) 9px 10px, transparent 10px 17px); }
    .photo[data-material="leather"] { background-color: var(--color-leather); }
    .photo[data-material="leather"]::before { opacity: 1; mix-blend-mode: normal; background-image: radial-gradient(circle at center, color-mix(in oklab, black 35%, transparent) 0 2.5px, transparent 3.5px); background-size: 25% 25%; background-position: 0 0; }

    /* --- Focus, hover, motion ------------------------------------------------
       One focus ring everywhere: cognac ink, 2px, offset by the page colour.
       Interactive cards lift; nothing scales or bounces; textures never animate. */
    :where(a, button, input, select, textarea, summary, [tabindex], .btn, .tab, .checkbox, .radio, .toggle):focus-visible {
      outline: 2px solid var(--color-accent-ink) !important; outline-offset: 2px !important; box-shadow: none;
    }
    :where(a, .btn, .card, .badge, .menu a, summary, .tab, input, select, textarea, .toggle, .checkbox, .radio) {
      transition-property: color, background-color, border-color, box-shadow, transform, opacity;
      transition-duration: var(--dur-quick); transition-timing-function: var(--ease-tailored);
    }
    label.input > input:focus-visible { outline-style: none !important; }
    label.input:focus-within { outline: 2px solid var(--color-accent-ink); outline-offset: 2px; }
    /* On a filled surface the ring takes that surface's content colour, which is
       guaranteed legible against it (the token build checks every pair). */
    .bg-neutral :focus-visible { outline-color: var(--color-neutral-content) !important; }
    .bg-secondary :focus-visible { outline-color: var(--color-secondary-content) !important; }
    .bg-leather :focus-visible { outline-color: var(--color-leather-content) !important; }
    .bg-primary :focus-visible { outline-color: var(--color-primary-content) !important; }
    :is(.bg-neutral, .bg-secondary, .bg-leather) .btn-ghost { color: inherit; }
    :is(.bg-neutral, .bg-secondary, .bg-leather) .btn-ghost:is(:hover, :focus-visible) { background-color: color-mix(in oklab, currentColor 14%, transparent); border-color: transparent; color: inherit; }
    :where(a.card, .card-link, [data-lift]) { transition-duration: var(--dur-settle); }
    :where(a.card, .card-link, [data-lift]):hover { box-shadow: var(--shadow-raised); transform: translateY(-1px); }
    body { transition: background-color var(--dur-room) var(--ease-tailored), color var(--dur-room) var(--ease-tailored); }
    /* Number inputs: no spinners; digits use tabular figures. */
    input[type="number"] { -moz-appearance: textfield; appearance: textfield; font-variant-numeric: tabular-nums; }
    input[type="number"]::-webkit-inner-spin-button, input[type="number"]::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
    input[data-money] { font-variant-numeric: tabular-nums; }

    /* --- Identity ----------------------------------------------------------
       Marks are outlined SVG (never retyped), fill currentColor, so they take the
       content colour of whatever surface they sit on. The full stop is cherry wood.
       Size with a height utility (h-6, h-10); width follows. Minimum 16px tall;
       clear space around the mark is half its height.                         */
    .mark { display: inline-block; block-size: 1em; inline-size: auto; flex: none; vertical-align: -0.1em; overflow: visible; }
    .mark .mark-dot { fill: var(--color-wood); }
    .mark-lg { block-size: 4.5rem; }
    .lockup { display: inline-flex; align-items: center; gap: 0.6em; line-height: 1; }

    /* --- Charts --------------------------------------------------------------
       One focus colour against quiet context; direct labels, no legends; at most
       four series and one cognac accent. HTML and CSS marks (themed by tokens, kept in
       print even with background graphics off); inline SVG only for lines. Wrap in figure[data-pattern="chart"] with a
       message title, a subtitle, and a source line (R16).                         */
    .chart { display: flex; flex-direction: column; gap: 0.9rem; }
    .chart-body { display: flex; flex-direction: column; gap: 0.6rem; }
    /* Bars: label, track, value. Width comes from --v and --max on the row. */
    .chart-bar { display: grid; grid-template-columns: minmax(7rem, 32%) 1fr auto; align-items: center; gap: 0.75rem; }
    /* Narrow containers: the label sits above its bar. */
    /* Bars share one set of columns (subgrid), so every track starts and ends at the same place
       whatever the length of its label or value. */
    .chart-body:has(> .chart-bar) { display: grid; grid-template-columns: minmax(7rem, 32%) 1fr auto; column-gap: 0.75rem; row-gap: 0.6rem; align-items: center; }
    .chart-body > .chart-bar { grid-column: 1 / -1; grid-template-columns: subgrid; }
    @container (max-width: 22rem) {
      .chart-body:has(> .chart-bar) { grid-template-columns: 1fr auto; }
      .chart-bar { grid-template-columns: 1fr auto; } .chart-bar > .chart-label { grid-column: 1 / -1; }
    }
    .chart { container-type: inline-size; }
    .chart-bar > .chart-label { font-size: calc(var(--step--1) * var(--type-scale)); color: var(--color-base-content); line-height: 1.3; }
    .chart-bar > .chart-track { block-size: 0.875rem; background-color: color-mix(in oklab, var(--color-chart-grid) 55%, transparent); border-radius: 2px; overflow: hidden; }
    .chart-bar > .chart-track > span { display: block; block-size: 100%; inline-size: calc(var(--v) / var(--max) * 100%); background-color: var(--color-chart-context); border-radius: 2px; }
    .chart-bar.is-focus > .chart-track > span { background-color: var(--color-chart-focus); }
    .chart-bar.is-accent > .chart-track > span { background-color: var(--color-chart-accent); }
    .chart-bar > .chart-value { font-family: var(--font-body); font-variant-numeric: tabular-nums; font-weight: 600; white-space: nowrap; }
    /* Part of a whole: one bar split into labelled segments sized by --v. */
    .chart-part { display: flex; gap: 3px; block-size: 1.25rem; }
    .chart-part > span, .chart-part-labels > span { flex: var(--v) 1 0; min-inline-size: 0; }
    .chart-part > span { background-color: var(--color-chart-context); border-radius: 2px; }
    .chart-part > span.is-focus { background-color: var(--color-chart-focus); }
    .chart-part > span.is-accent { background-color: var(--color-chart-accent); }
    .chart-part-labels { display: flex; gap: 3px; }
    .chart-part-labels > span { font-size: calc(var(--step--1) * var(--type-scale)); line-height: 1.3; }
    .chart-part-labels b { display: block; font-family: var(--font-display); font-weight: 400; font-size: calc(var(--step-2) * var(--type-scale)); line-height: 1.1; }
    /* Units: one square per unit (weeks, nights, people). */
    .chart-units { display: grid; grid-template-columns: repeat(var(--cols, 13), 1fr); gap: 3px; max-inline-size: 28rem; }
    .chart-units > span { aspect-ratio: 1; border-radius: 2px; box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--color-base-content) 50%, transparent); }
    .chart-units > span.on { background-color: var(--color-chart-focus); box-shadow: none; }
    /* Lines: inline SVG, drawn in currentColor so an ink utility sets the colour. */
    .chart-spark { inline-size: 5em; block-size: 1.25em; vertical-align: -0.25em; overflow: visible; }
    .chart-spark :is(polyline, path) { fill: none; stroke: currentColor; stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; vector-effect: non-scaling-stroke; }
    /* Slope: before and after. Place each end at its value with --y (0 top, 100 bottom).
       Three columns: the start labels, the line, the end labels. The label columns grow to fit
       their text (at least 8.5rem), so a long label never hangs outside the chart. Each label
       rides in a zero-height row above its figure, so the line meets the figure's middle. */
    .chart-slope { display: grid; grid-template-columns: minmax(8.5rem, max-content) minmax(0, 1fr) minmax(8.5rem, max-content); grid-template-rows: minmax(0, 1fr); column-gap: 1rem; block-size: var(--h, 14rem); }
    .chart-slope svg { grid-area: 1 / 2; inline-size: 100%; block-size: 100%; overflow: visible; }
    .chart-slope line { stroke-width: 2.5; stroke-linecap: round; vector-effect: non-scaling-stroke; }
    .chart-slope :is(.chart-from, .chart-to) { position: relative; align-self: start; top: calc(var(--y) * 1%); transform: translateY(-50%); display: grid; grid-template-rows: 0 auto; }
    .chart-slope :is(.chart-from, .chart-to) > .t-label { align-self: end; white-space: nowrap; }
    .chart-slope .chart-from { grid-area: 1 / 1; text-align: end; }
    .chart-slope .chart-to { grid-area: 1 / 3; }

    /* --- Reading and frames ------------------------------------------------ */

    /* Measure: the comfortable line length for running text. */
    .measure { max-width: var(--measure); }
    /* Flow: paragraph rhythm for long-form text (the Tailwind typography plugin is not on the CDN). */
    .flow > * + * { margin-block-start: var(--flow-space); }
    .flow > * + :is(h2, h3, .t-title, .t-heading) { margin-block-start: calc(var(--flow-space) * 2.25); }
    .flow > :is(h2, h3, .t-title, .t-heading, .t-subhead) + * { margin-block-start: calc(var(--flow-space) * 0.6); }
    :is(ul, ol).flow, .flow :is(ul, ol) { padding-inline-start: 1.25em; }
    ul.flow, .flow ul { list-style: disc; }
    ol.flow, .flow ol { list-style: decimal; }
    :is(ul, ol).flow > li + li, .flow li + li { margin-block-start: calc(var(--flow-space) * 0.35); }
    .flow li::marker { color: var(--color-subtle); }

    /* Sheet: a printable document. Sets the page box; break rules are in the print block below. */
    .sheet { page: sheet; }

    /* Slide: one fixed 16:9 frame. Its content is laid out on a 1280 x 720 design
       canvas and scaled as a whole to the slide's width, so a slide is identical as a
       thumbnail, on a phone, full screen and on paper. tan(atan2(a, b)) turns the two
       lengths into the unitless ratio that scale() needs. */
    .slide {
      container-type: inline-size;
      position: relative;
      aspect-ratio: 16 / 9;
      overflow: hidden;
      page: slide;
      break-after: page;
      break-inside: avoid;
      --type-scale: 1.5;
      --slide-scale: tan(atan2(100cqw, 1280px));
    }
    .slide-body {
      position: absolute; top: 0; left: 0; width: 1280px; height: 720px;
      display: flex; flex-direction: column; gap: 32px; padding: 64px 80px 92px;
      transform-origin: 0 0; transform: scale(var(--slide-scale));
    }
    .slide-footer {
      position: absolute; left: 0; bottom: 0; width: 1280px; padding: 0 80px 32px;
      display: flex; justify-content: space-between;
      transform-origin: 0 100%; transform: scale(var(--slide-scale));
    }
    /* On the fixed canvas the fluid roles take fixed steps (no viewport units). */
    .slide .t-display { font-size: calc(var(--step-5) * var(--type-scale)); }
    .slide .t-title   { font-size: calc(var(--step-3) * var(--type-scale)); }
    /* Optical size follows the size a slide is actually shown at, not its 1280px design
       canvas, so a slide on a phone uses Fraunces' sturdier small-size cut. */
    .slide :is(.t-display, .t-title, .t-heading, .t-figure, .t-figure-sm, .t-figure-md, .t-figure-lg, .t-hero) {
      font-optical-sizing: none;
      font-variation-settings: var(--_fvs, var(--font-display-settings)), "opsz" clamp(9, calc(var(--_fs, 40) * var(--slide-scale)), 144);
    }
    .slide .t-hero { --_fs: 243; }
    .slide .t-display, .slide .t-figure-lg { --_fs: 89; }
    .slide .t-figure { --_fs: 69; }
    .slide :is(.t-title, .t-figure-md) { --_fs: 53; }
    .slide .t-figure-sm { --_fs: 41; }
    .slide .t-heading { --_fs: 31; }
    .slide .measure   { max-width: 1000px; }
    /* daisyUI parts are sized for apps; on the slide canvas they get room-sized type (body
       24px, context lines about 22px at 1280 wide). Text that carries or inherits a t-* role keeps it. */
    .slide .stats { overflow: visible; }
    .slide .stat { padding: 32px 40px; }
    .slide .stat-desc { font-size: calc(var(--step-0) * var(--type-scale) * 0.9); white-space: normal; }
    .slide .badge { font-size: calc(var(--step--1) * var(--type-scale)); height: auto; padding: 4px 14px; }
    .slide :where(li, p, dd, td, th):not(
      :where(.t-display, .t-title, .t-heading, .t-lead, .t-subhead, .t-body, .t-caption, .t-label, .t-eyebrow, .t-mono, .t-figure, .t-figure-sm, .t-figure-md, .t-figure-lg, .t-numeral, .t-hero),
      :where(.t-lead, .t-body, .t-caption, .t-label) *
    ) { font-size: calc(var(--step-0) * var(--type-scale)); }

    /* --- Seam details ------------------------------------------------------ */

    /* Stitch — a dashed upholstery seam set just inside the edge. Drawn with
       ::after so the element's own outline stays free for focus and selection. */
    .stitch { position: relative; }
    .stitch::after {
      content: "";
      position: absolute;
      inset: var(--stitch-inset);
      border: 1px dashed color-mix(in oklab, currentColor calc(38% * var(--texture)), transparent);
      border-radius: max(0px, calc(var(--radius-box) - 4px));
      pointer-events: none;
    }

    /* Wood rule — a cherry inlay divider. */
    .rule-wood {
      height: 3px;
      border: 0;
      border-radius: 999px;
      background-color: var(--color-wood);
    }

    /* Hardware — brushed nickel hairline for frames and dividers. */
    .edge-nickel { border: var(--border) solid var(--color-nickel); }
  }
}

/* --- Print ------------------------------------------------------------------
   Documents and decks get printed and exported to PDF. By default print is
   ink-friendly: textures off, dark and leather surfaces become bordered light
   panels. Add .print-exact to a surface (or the page root) to keep it as
   designed; browsers are then told not to drop its background.            */
/* Page boxes. Named pages keep documents portrait and slides one per landscape page.
   Running headers via @page margin boxes are not relied on; put colophons in the flow. */
@page { margin: 18mm; }
@page sheet {
  margin: 18mm 18mm 22mm;
  /* Page X of Y (Chrome 131+; other browsers simply omit it; the colophon stays in the flow). */
  @bottom-right { content: counter(page) " of " counter(pages); font-size: 8.5pt; font-family: "Instrument Sans", system-ui, sans-serif; }
}
@page sheet:first { @bottom-right { content: none; } }
@page slide { size: 13.333in 7.5in; margin: 0; }

@media print {
  :root, [data-theme] { --texture: 0; }
  /* Deterministic breaks in every format: headings stay with their text, blocks do not split. */
  :is(h1, h2, h3, h4, .t-title, .t-heading, .t-subhead, .t-eyebrow) { break-after: avoid; }
  /* Card-sized blocks stay whole; long patterns (timelines, checklists, comparisons) may split between rows. */
  /* Options sit side by side on paper, as they do on a wide screen, and stay on one page. */
  [data-pattern="comparison-grid"] { break-inside: avoid; }
  [data-pattern="comparison-grid"] > .grid { grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); }
  :is(figure, table, dl, .card, .stats, .alert, li, tr, blockquote, [data-pattern="booking-card"], [data-pattern="key-takeaways"], [data-pattern="stat-row"], [data-pattern="key-value-summary"], [data-pattern="empty-state"]) { break-inside: avoid; }
  p, li { orphans: 3; widows: 3; }
  /* Chart marks are backgrounds: keep them when "Background graphics" is off. */
  :is(.chart-track > span, .chart-part > span, .chart-units > span, .stop-num, .route-map) { print-color-adjust: exact; -webkit-print-color-adjust: exact; }
  /* The seam's welt is a background layer; drop it in ink-friendly print so boxes never print blank. */
  .seam:not(.print-exact, .print-exact *)::after { background-image: none; }
  /* Keep a section's heading with its opening lines and a note beside its paragraph;
     whole sections may still split, so pages fill instead of leaving half-empty sheets. */
  .numbered h2.t-title, .margin-note { break-inside: avoid; }
  section[data-date] > :is(header, h2, h3, :first-child) { break-after: avoid; }
  .route-map { max-block-size: 55mm; }
  .slide { width: 13.333in; height: 7.5in; border: 0 !important; border-radius: 0 !important; }
  :is(.bg-neutral, .bg-leather, .bg-primary, .bg-secondary, .bg-accent):not(.print-exact, .print-exact *) {
    background-color: transparent !important;
    color: var(--color-base-content) !important;
    border: var(--border) solid var(--color-base-content);
  }
  /* Text inside a converted panel must follow it, even if it asked for a content colour. */
  :is(.bg-neutral, .bg-leather, .bg-primary, .bg-secondary, .bg-accent):not(.print-exact, .print-exact *) * {
    color: inherit !important;
  }
  .print-exact, .print-exact * { print-color-adjust: exact; -webkit-print-color-adjust: exact; }
  /* Opt-in surfaces get textures back at full strength, including daisyUI grain. */
  .print-exact, .print-exact * { --texture: 1; --noise: 1; }
}

/* A page (or the library switch) can ask for reduced motion with data-reduce-motion on <html>. */
[data-reduce-motion] *, [data-reduce-motion] *::before, [data-reduce-motion] *::after { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; scroll-behavior: auto !important; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; }
}
