/*! Ahimsa Design System v1.0.0 — Day + Night
 *  MIT. Fonts are SIL OFL-1.1; ship fonts/OFL-*.txt alongside them.
 *  https://ahimsa-design.riverma.com
 *  Generated by tools/build.mjs — edit src/, not this file. */

/* ===== fonts/day.css ===== */
/* Ahimsa Day webfonts — self-hosted, SIL Open Font License 1.1.
   Licence texts travel with the binaries: fonts/OFL-Fraunces.txt,
   fonts/OFL-DMSans.txt. Variable fonts, Latin subset. No network requests,
   ever — a Google Fonts @import would break every offline consumer, and
   would fail their build checks outright.

   Paths assume the fonts/ directory sits at the site root. If yours lives
   elsewhere, rewrite these five urls; CSS cannot interpolate a variable
   into url(). */

@font-face {
  font-family: "Fraunces";
  src: url("./fonts/Fraunces-VariableFont.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Fraunces";
  src: url("./fonts/Fraunces-Italic-VariableFont.woff2") format("woff2");
  font-weight: 100 900;
  font-style: italic;
  font-display: swap;
}
@font-face {
  font-family: "DM Sans";
  src: url("./fonts/DMSans-VariableFont.woff2") format("woff2");
  font-weight: 100 1000;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "DM Sans";
  src: url("./fonts/DMSans-Italic-VariableFont.woff2") format("woff2");
  font-weight: 100 1000;
  font-style: italic;
  font-display: swap;
}

/* ===== fonts/night.css ===== */
/* Ahimsa Night webfonts — self-hosted, SIL Open Font License 1.1.
   Licence text at fonts/OFL-DMMono.txt.

   Night needs DM Sans (declared in day.css — import both files if you use
   both themes) plus DM Mono for the utility voice. DM Mono is not a
   variable font, so Regular and Medium ship as separate files; they are
   subset to Latin and come in at ~9 KB each. */

@font-face {
  font-family: "DM Mono";
  src: url("./fonts/DMMono-Regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "DM Mono";
  src: url("./fonts/DMMono-Medium.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

/* ===== tokens/shared.css ===== */
/* Ahimsa — shared tokens.
   Geometry and time. These do NOT change between Day and Night: switching
   aesthetic changes colour, type voice and surface character, never the
   grid, the corners or the timing. That stability is why both themes read
   as one system.

   Sizes are in rem, at the same computed values the system has always
   rendered (13px = 0.8125rem at a 16px root). px would silently override
   a person's own browser font-size setting, which is the opposite of what
   this system is for. See docs/accessibility. */

:root {
  /* ---- Spacing — 4px grid. Generous negative space is a core principle;
     err toward the larger step. If a section feels crowded, remove
     elements before shrinking type. ---- */
  --space-0:  0px;
  --space-1:  4px;
  --space-2:  8px;
  --space-3:  12px;
  --space-4:  16px;
  --space-5:  20px;
  --space-6:  24px;
  --space-7:  28px;
  --space-8:  32px;
  --space-10: 40px;
  --space-12: 48px;
  --space-16: 64px;
  --space-20: 80px;
  --space-24: 96px;

  /* ---- Radius — rounded at every scale, never sharp. Sharp corners in UI
     carry an implicit threat; rounded corners carry receptiveness. This is
     a system law, not a Day preference. ---- */
  --radius-xs:   4px;    /* small pill-adjacent elements */
  --radius-sm:   8px;    /* small buttons, tags */
  --radius-md:   12px;   /* standard inputs, small cards */
  --radius-lg:   16px;   /* cards */
  --radius-xl:   20px;   /* featured cards */
  --radius-2xl:  24px;   /* bottom sheets, panels */
  --radius-3xl:  32px;   /* large frames, phone-inset outer */
  --radius-full: 9999px; /* pills, monograms, circular buttons */

  /* ---- Type sizes — a musical, deliberately-uneven scale. Note there is
     no 12px or 14px step; the gaps are the point. ---- */
  --size-micro: 0.5625rem; /* 9px  */
  --size-xs:    0.625rem;  /* 10px */
  --size-sm:    0.6875rem; /* 11px */
  --size-base:  0.8125rem; /* 13px — a LABEL size, not a reading size */
  --size-md:    0.9375rem; /* 15px */
  --size-lg:    1.0625rem; /* 17px */
  --size-xl:    1.375rem;  /* 22px */
  --size-2xl:   1.75rem;   /* 28px */
  --size-3xl:   2.125rem;  /* 34px */
  --size-4xl:   2.5rem;    /* 40px */
  --size-5xl:   3rem;      /* 48px */

  /* Running prose uses this, always. --size-base is for UI labels. */
  --size-reading: 1rem;    /* 16px */

  /* Fluid display, for editorial moments that want to fill a viewport.
     Both themes; the tracking differs (Day pulls tight, Night opens up). */
  --size-fluid-display: clamp(2.75rem, 9vw, 7.5rem);
  --size-fluid-heading: clamp(1.75rem, 4.5vw, 3.25rem);

  /* ---- Weights ---- */
  --weight-extralight: 200;
  --weight-light:      300;
  --weight-regular:    400;
  --weight-medium:     500;
  --weight-semibold:   600;
  --weight-bold:       700;

  /* ---- Line heights ---- */
  --leading-tight:   1.0;  /* large display type */
  --leading-snug:    1.15; /* headings */
  --leading-base:    1.4;  /* body copy */
  --leading-relaxed: 1.55; /* long reads */

  /* ---- Letter spacing. Day pulls display tight and opens small caps
     wide; Night inverts the display end. ---- */
  --tracking-tightest: -0.02em;  /* very large display */
  --tracking-tight:    -0.01em;  /* serif headings */
  --tracking-normal:   0;
  --tracking-wide:     0.15em;   /* small caps utility labels */
  --tracking-wider:    0.22em;   /* standard small caps */
  --tracking-widest:   0.32em;   /* very quiet meta */
  --tracking-letter:   0.4em;    /* sparse — dividers, section labels */

  /* ---- Layout ---- */
  --gutter:       20px;
  --gutter-fluid: clamp(20px, 5.2vw, 106px); /* opt-in editorial mode */

  /* ---- Motion — gentle and decelerating. Nothing snaps, nothing
     overshoots. Motion mimics settling, not predator movement. Every
     easing below ends at 1.0, with no value above it. ---- */
  --duration-instant: 100ms;
  --duration-fast:    200ms;
  --duration-base:    300ms;
  --duration-slow:    400ms;
  --duration-slower:  550ms; /* sheet slide-ups, card flips */
  --duration-slowest: 800ms; /* onboarding transitions */
  --duration-reveal:  620ms; /* clip-path reveals of large surfaces */

  --ease-standard:   cubic-bezier(0.4, 0.0, 0.2, 1.0);    /* small state changes */
  --ease-emphasized: cubic-bezier(0.32, 0.72, 0.32, 1.0); /* carousels, card reveals, sheets */
  --ease-decelerate: cubic-bezier(0.0, 0.0, 0.2, 1.0);    /* entering elements */
  --ease-accelerate: cubic-bezier(0.4, 0.0, 1.0, 1.0);    /* exiting elements */
  --ease-reveal:     cubic-bezier(0.76, 0, 0.24, 1);      /* clip-path wipes */
  --ease-expo:       cubic-bezier(0.22, 1, 0.36, 1);      /* long, soft settle */

  /* ---- The growing underline (.ah-link) ---- */
  --underline-thickness: max(1.5px, 0.045em);
  --underline-gap:       0.22em;
  --underline-duration:  220ms;
  --underline-ease:      cubic-bezier(0.65, 0, 0.35, 1);
}

/* ===== tokens/day/palette.css ===== */
/* Ahimsa Day — raw palette.
   Warm cream neutrals and muted, warm-adjusted accents. These are raw
   values; components must reference the semantic aliases in semantic.css,
   never a step from this file.

   Never pure white, never pure black. There is intentionally NO bright-red
   danger value anywhere in this palette — errors use warm clay. */

.ahimsa-day {
  /* ---- Neutral: warm cream -> warm dark brown. The workhorse. ---- */
  --neutral-50:  #faf8f5; /* softest cream — elevated surfaces, sheets, cards */
  --neutral-100: #f4f0ea; /* very light cream — inline elevated blocks */
  --neutral-200: #ebe6df; /* canvas — the default app background */
  --neutral-300: #e0dcd5; /* sunk / recessed surfaces */
  --neutral-400: #b8b0a4; /* disabled state, faint text */
  --neutral-500: #8a8078; /* muted mid-tone */
  --neutral-600: #605850; /* secondary text */
  --neutral-700: #3d3428; /* body text */
  --neutral-800: #2a1f18; /* heading text — darkest warm brown */
  --neutral-900: #1a1310; /* inverse surface */

  /* ================================================================
     The ahimsa-tradition accents — the recommended family.
     Named for the tradition the system takes its ethics from:
     saffron (renunciate robes), turmeric (ritual gold), clay (earthen
     lamps), bodhi (the fig tree of awakening), peacock (India's bird),
     indigo (the Champaran satyagraha crop), monsoon (grey-blue rain
     skies). Nothing high-chroma, nothing "poppy".
     ================================================================ */

  --saffron-300: #f8b877;
  --saffron-400: #f29e4e;
  --saffron-500: #e8862e;
  --saffron-600: #c96d1e; /* primary saffron — marigold-robe orange */
  --saffron-700: #a05415;

  --turmeric-300: #edd07e;
  --turmeric-400: #e3b94f;
  --turmeric-500: #d4a02b;
  --turmeric-600: #b3841f;
  --turmeric-700: #8d6716;

  --clay-300: #dda184;
  --clay-400: #cd8060;
  --clay-500: #b96144;
  --clay-600: #9c4c33; /* fired-earth terracotta */
  --clay-700: #7c3a27;

  --bodhi-300: #b5cf9e;
  --bodhi-400: #93b976;
  --bodhi-500: #729e53;
  --bodhi-600: #5a8340;
  --bodhi-700: #456631;

  --peacock-300: #8ecbd1;
  --peacock-400: #5fb0bb;
  --peacock-500: #3b93a3;
  --peacock-600: #2c7787;
  --peacock-700: #215d6b;

  --indigo-300: #9ba4d6;
  --indigo-400: #7480c4;
  --indigo-500: #545fae;
  --indigo-600: #414a92;
  --indigo-700: #313876;

  --monsoon-300: #9dadc2;
  --monsoon-400: #7a8fab;
  --monsoon-500: #5d7492;
  --monsoon-600: #475b77;
  --monsoon-700: #36465d;

  /* ================================================================
     The classic accents — the alternate family.
     These are the original Ahimsa accents, kept because a consumer built
     a full set of person-gradients directly on them. Same muted, warm-
     adjusted register; reach for the tradition set first.

     NOTE the one rename: this family's blue-violet is --periwinkle-*.
     It was historically called --indigo-* in one consumer, which
     collided with the tradition set's darker indigo at a different
     value. Tradition indigo keeps the name; this one moved.
     ================================================================ */

  --amber-300: #f5c391;
  --amber-400: #ecb078;
  --amber-500: #d4a574;
  --amber-600: #b8895a;
  --amber-700: #96683f;

  --rose-300: #f4bdbf;
  --rose-400: #ed9fa5;
  --rose-500: #e08591;
  --rose-600: #c26875;
  --rose-700: #9f4e5a;

  --terracotta-300: #d6aa95;
  --terracotta-400: #c89178;
  --terracotta-500: #b87859;
  --terracotta-600: #99613f;
  --terracotta-700: #7a4a2d;

  --sage-300: #b9cdc0;
  --sage-400: #9ab5a0;
  --sage-500: #7a9d7f;
  --sage-600: #5f8467;
  --sage-700: #47694f;

  --violet-300: #cebede;
  --violet-400: #bba8d2;
  --violet-500: #a692c4;
  --violet-600: #8c78ac;
  --violet-700: #705d90;

  --slate-300: #a5b3c7;
  --slate-400: #8499b5;
  --slate-500: #6781a3;
  --slate-600: #4c6788;
  --slate-700: #3a516d;

  --periwinkle-300: #b0b6df;
  --periwinkle-400: #8590cf;
  --periwinkle-500: #6571b8;
  --periwinkle-600: #4d5aa0;
  --periwinkle-700: #3a4488;
}

/* ===== tokens/day/gradients.css ===== */
/* Ahimsa Day — signature gradients.
   All 155 degrees (cream is 170 for large horizontal surfaces), stops
   always at 0 / 30 / 60 / 100%. Named by feeling.

   The rule: ONE GRADIENT OWNS A SCREEN. Never two side by side. Flat cream
   is the default; a gradient is a moment of emphasis, not a decoration.

   No repeating patterns, no textures, no photography direction. */

.ahimsa-day {
  /* ---- The two neutral heroes ---- */
  --gradient-dawn:  linear-gradient(155deg, #f4a578 0%, #f5be8e 30%, #f6d3a8 60%, #fae7c6 100%);
  --gradient-cream: linear-gradient(170deg, #fdf6e8 0%, #f9ebcf 50%, #f3deb3 100%);

  /* ---- The ahimsa-tradition set ---- */
  --gradient-saffron:  linear-gradient(155deg, #e8862e 0%, #f0a256 30%, #f6bd81 60%, #fad8ae 100%);
  --gradient-turmeric: linear-gradient(155deg, #d4a02b 0%, #dfb455 30%, #e9c982 60%, #f2ddb0 100%);
  --gradient-clay:     linear-gradient(155deg, #b96144 0%, #cd8060 30%, #dda184 60%, #ecc3ad 100%);
  --gradient-bodhi:    linear-gradient(155deg, #729e53 0%, #93b976 30%, #b5cf9e 60%, #d6e4c8 100%);
  --gradient-peacock:  linear-gradient(155deg, #3b93a3 0%, #5fb0bb 30%, #8ecbd1 60%, #bee2e4 100%);
  --gradient-indigo:   linear-gradient(155deg, #545fae 0%, #7480c4 30%, #9ba4d6 60%, #c5c9e6 100%);
  --gradient-monsoon:  linear-gradient(155deg, #5d7492 0%, #7a8fab 30%, #9dadc2 60%, #c2cbd8 100%);

  /* ---- The classic set ---- */
  --gradient-amber:            linear-gradient(155deg, #d4a574 0%, #e0b888 30%, #ecc99c 60%, #f5dcb3 100%);
  --gradient-rose:             linear-gradient(155deg, #e08591 0%, #ed9fa5 30%, #f4bdbf 60%, #fadcd9 100%);
  --gradient-coral:            linear-gradient(155deg, #e88b6a 0%, #efa181 30%, #f5b89c 60%, #fad0bb 100%);
  --gradient-terracotta:       linear-gradient(155deg, #b87859 0%, #c89178 30%, #d6aa95 60%, #e3c3b3 100%);
  --gradient-sage:             linear-gradient(155deg, #7a9d7f 0%, #9ab5a0 30%, #b9cdc0 60%, #d7e3da 100%);
  --gradient-teal:             linear-gradient(155deg, #5db8a8 0%, #82c8bb 30%, #a8d8cd 60%, #cee8e0 100%);
  --gradient-violet:           linear-gradient(155deg, #a692c4 0%, #bba8d2 30%, #cebede 60%, #e0d4ea 100%);
  --gradient-slate:            linear-gradient(155deg, #6781a3 0%, #8499b5 30%, #a5b3c7 60%, #c5cdd8 100%);
  --gradient-periwinkle:       linear-gradient(155deg, #6571b8 0%, #8590cf 30%, #b0b6df 60%, #d3cce6 100%);
  --gradient-periwinkle-soft:  linear-gradient(155deg, #6571b8 0%, #8590cf 30%, #a8b1de 60%, #c8cee9 100%);

  /* ---- Gradient-only hues. No 5-step ramp was ever authored for these,
     and Ahimsa's gradients are independently designed rather than derived
     from the palettes, so inventing ramps here would be a fabrication.
     Use them as surfaces, not as ink. ---- */
  --gradient-mauve: linear-gradient(155deg, #c4799a 0%, #d494b0 30%, #e5b5c6 60%, #f2d7dd 100%);
  --gradient-olive: linear-gradient(155deg, #7d9963 0%, #97ae82 30%, #b0c29c 60%, #c7d0b5 100%);
}

/* ===== tokens/day/semantic.css ===== */
/* Ahimsa Day — semantic tokens.
   The ONLY colour tokens a component may reference. Both themes define
   this exact set of names, which is what makes markup theme-portable. */

.ahimsa-day {
  /* ---- Surfaces ---- */
  --surface-canvas:      var(--neutral-200); /* default app background */
  --surface-canvas-flat: #ebe6df;            /* for html/body, where a gradient can't go */
  --surface-elevated:    var(--neutral-50);  /* cards, sheets, elevated surfaces */
  --surface-sunk:        var(--neutral-300); /* recessed surfaces, subtle wells */
  --surface-inverse:     var(--neutral-800); /* dark surface, used sparingly */

  /* ---- Text. Quiet steps are the warm brown at low alpha rather than a
     grey, so nothing ever reads cold against cream.

     DEVIATION FROM THE ORIGINAL TOKEN SET, and the only one in Day.
     The original alphas were 65 / 45 / 28%, which measure 3.64 / 2.30 /
     1.63 against the darkest Day ground — secondary fails AA outright and
     muted fails even the 3:1 large-text floor. They are not usable for
     descriptions or labels, which is what they are for.

     The values below are 76 / 60 / 40%, which measure 4.79 / 3.23 / 2.07.
     They are not invented: a shipping consumer independently derived
     exactly these, for a different reason (quiet text fading on a warm
     gradient canvas), and they turn out to be the accessible ones.
     tools/check-parity.mjs records all three as named deviations, so the
     gate still catches any change nobody decided on. ---- */
  --text-heading:   var(--neutral-800); /* titles, display type */
  --text-body:      var(--neutral-700); /* body copy */
  --text-secondary: #3d3428c2;          /* 76% — descriptions, captions. 4.79:1 worst case */
  --text-muted:     #3d342899; /* NON-TEXT ONLY — status dots, rules, disabled marks.
                                   Measures 3.23:1, which clears 3:1 for non-text
                                   (1.4.11) but NOT the 4.5:1 small text owes. No component
                                   sets type in this. */
  --text-faint:     #3d342866;          /* 40% — hints ONLY. 2.07:1: never the sole carrier of anything */
  --text-inverse:   var(--neutral-50);  /* text on dark surfaces */

  /* ---- Borders. Mostly absent by design: inputs use a single bottom rule
     rather than a box, and separators are warm brown at low alpha. ---- */
  --border-subtle: #3d342814; /* ~8%  — barely-there divisions */
  --border-medium: #3d34282e; /* ~18% — clear separators */
  --border-strong: #3d342859; /* ~35% — emphatic, but DECORATIVE only: 1.90:1 */

  /* The boundary of an interactive control — a ghost button's ring, an
     outline pill. That line IS the control's edge, so it owes 3:1
     (WCAG 1.4.11) in a way a separator does not. --border-strong cannot
     carry it. Additive; nothing upstream had this. */
  --border-control: #3d342899; /* 60% — 3.23:1 worst case */

  /* ---- Accents ---- */
  --accent-primary:   var(--saffron-600); /* brand — primary CTA */
  --accent-secondary: var(--indigo-500);  /* interactive — links, active tabs */
  --accent-success:   var(--bodhi-600);
  --accent-warning:   var(--clay-600);    /* the ONLY error colour. Warm, not alarming.
                                             There is no red token in this system. */

  --accent-warning-surface: var(--clay-600); /* fill for a destructive control — fired earth, not red.
                                      clay-500 was the inherited choice and puts the label at
                                      4.08:1; -600 reaches 5.67:1 and is no more alarming.
                                      A destructive action is still not an emergency. */

  /* Setting SMALL TEXT in an accent? Use the -700 step, not these.
     --accent-success and --accent-warning are tuned as fills and marks and
     clear the 3:1 non-text bar; as 10px type they do not reach 4.5:1. */

  /* ---- Focus. Must clear 3:1 against both the element and its ground
     (WCAG 1.4.11). Never suppressed — see base.css. ---- */
  --focus-ring:        var(--saffron-700);
  --focus-ring-offset: var(--surface-canvas-flat);
  --focus-ring-width:  2px;

  /* ---- Glass. Needs backdrop-filter to read correctly. Reserved for
     surfaces sitting on a gradient, and for the sheet scrim. Not used
     decoratively on flat cream. ---- */
  --glass-overlay:        #ffffff6b; /* 42% white — frosted glass surfaces */
  --glass-overlay-strong: #ffffff8f; /* 56% white — more opaque frosted panels */
  --glass-scrim:          #140f0a73; /* 45% dark — modal / sheet scrims */
  --glass-blur:           12px;

  /* ---- Glow. Day has none: depth here comes from warm paper shadow.
     Declared so components can reference it unconditionally. ---- */
  --glow-sm: none;
  --glow-md: none;
  --glow-lg: none;

  /* ---- Day's display type pulls tight. Night opens up instead. ---- */
  --tracking-display: -0.045em;
}

/* ===== tokens/day/elevation.css ===== */
/* Ahimsa Day — elevation.
   Warm brown-tinted, low-opacity, layered. Never harsh, never black
   (except the one signature deep drop). --shadow-md carries an inner white
   highlight, which is what makes a card read as paper rather than as a
   floating rectangle. */

.ahimsa-day {
  --shadow-xs:    0 1px 2px #3d342814;
  --shadow-sm:    0 2px 8px #3d342821;
  --shadow-md:    0 4px 20px #3d342829, inset 0 1px 0 #ffffff99;
  --shadow-lg:    0 8px 32px #3d342833;
  --shadow-xl:    0 30px 60px -20px #00000059, 0 12px 24px -12px #00000033;
  --shadow-sheet: 0 -10px 40px #0000002e;
}

/* ===== tokens/day/type.css ===== */
/* Ahimsa Day — typography.
   Two families doing different jobs. Fraunces (variable serif) carries
   emotional weight: display type, titles, italic pull quotes. DM Sans
   handles utility: labels, small caps, buttons, dates.

   Two signature styles define the fingerprint:
     .ah-small-caps  — wide-tracked uppercase sans, the utility voice
     .ah-pull-quote  — italic serif, the emotional voice

   The split IS the tone. An emotional line goes in italics; a factual line
   stays upright. Never put quotation marks on a pull quote — the italic
   styling is already doing that job. */

.ahimsa-day {
  --font-display: "Fraunces", Georgia, "Iowan Old Style", serif;
  --font-body:    "DM Sans", -apple-system, system-ui, sans-serif;
  --font-mono:    ui-monospace, "SF Mono", Menlo, Consolas, monospace;
}

.ahimsa-day .ah-display-xl { font-family: var(--font-display); font-size: var(--size-5xl);   font-weight: 400; line-height: 1.0;  letter-spacing: -0.015em; }
.ahimsa-day .ah-display-l  { font-family: var(--font-display); font-size: var(--size-4xl);   font-weight: 400; line-height: 1.0;  letter-spacing: -0.015em; }
.ahimsa-day .ah-display-m  { font-family: var(--font-display); font-size: var(--size-3xl);   font-weight: 400; line-height: 1.05; letter-spacing: -0.01em; }
.ahimsa-day .ah-heading-l  { font-family: var(--font-display); font-size: var(--size-2xl);   font-weight: 400; line-height: 1.1;  letter-spacing: -0.01em; }
.ahimsa-day .ah-heading-m  { font-family: var(--font-display); font-size: var(--size-xl);    font-weight: 400; line-height: 1.15; }
.ahimsa-day .ah-title-l    { font-family: var(--font-display); font-size: var(--size-lg);    font-weight: 400; line-height: 1.2; }
.ahimsa-day .ah-title-m    { font-family: var(--font-display); font-size: var(--size-md);    font-weight: 400; line-height: 1.2; }
.ahimsa-day .ah-body       { font-family: var(--font-body);    font-size: var(--size-base);  font-weight: 400; line-height: 1.4; }
.ahimsa-day .ah-body-serif { font-family: var(--font-display); font-size: var(--size-md);    font-weight: 400; line-height: 1.55; }
.ahimsa-day .ah-caption    { font-family: var(--font-display); font-size: var(--size-sm);    font-weight: 300; line-height: 1.4;  font-style: italic; }
.ahimsa-day .ah-pull-quote { font-family: var(--font-display); font-size: var(--size-lg);    font-weight: 400; line-height: 1.35; font-style: italic; }
.ahimsa-day .ah-label      { font-family: var(--font-body);    font-size: var(--size-sm);    font-weight: 500; line-height: 1.2; }
.ahimsa-day .ah-small-caps { font-family: var(--font-body);    font-size: var(--size-xs);    font-weight: 500; line-height: 1.2;  letter-spacing: 0.22em; text-transform: uppercase; }
.ahimsa-day .ah-micro-caps { font-family: var(--font-body);    font-size: var(--size-micro); font-weight: 500; line-height: 1.2;  letter-spacing: 0.32em; text-transform: uppercase; }

/* The editorial moment: display type that fills a viewport, pulled tight.
   Day's display tracking is negative — the letters lean into each other. */
.ahimsa-day .ah-display-fluid {
  font-family: var(--font-display);
  font-size: var(--size-fluid-display);
  font-weight: 400;
  line-height: 0.92;
  letter-spacing: var(--tracking-display);
  text-wrap: balance;
}

/* Running prose. Mandatory for anything anyone actually reads: --size-base
   is a label size and always was. */
.ahimsa-day .ah-prose {
  font-family: var(--font-display);
  font-size: var(--size-reading);
  font-weight: 400;
  line-height: var(--leading-relaxed);
  text-wrap: pretty;
}

/* ===== tokens/day/surfaces.css ===== */
/* Ahimsa Day — gradient surfaces. GENERATED by tools/build-surfaces.mjs.
   Do not edit. Change the gradients or the generator instead.

   Each class sets a gradient background AND the ink that is measured to
   work on it. Put one on any element and its contents read correctly:

     <section class="ah-surface--dawn"> ... </section>

   Scoped to the theme, because a Day gradient and a Night gradient need
   different ink and two of them once shared a name. To show a Day surface
   inside a Night page (a specimen, say), wrap it: <div class="ahimsa-day">.

   ONE GRADIENT OWNS A SCREEN. Two of these side by side is the rule this
   system breaks least gladly. */

/* dawn — worst stop #f4a578; heading 9.17:1, body/secondary 4.52:1, muted 3.01:1 */
.ahimsa-day .ah-surface--dawn {
  background: var(--gradient-dawn);
  --text-heading:   #1a1310;
  --text-body:      #1a1310ad;
  --text-secondary: #1a1310ad;
  --text-muted:     #1a131084;
  --border-control: #1a13109e;
}

/* cream — worst stop #f3deb3; heading 13.90:1, body/secondary 4.52:1, muted 3.02:1 */
.ahimsa-day .ah-surface--cream {
  background: var(--gradient-cream);
  --text-heading:   #1a1310;
  --text-body:      #1a13109c;
  --text-secondary: #1a13109c;
  --text-muted:     #1a131079;
  --border-control: #1a131093;
}

/* saffron — worst stop #e8862e; heading 6.89:1, body/secondary 4.53:1, muted 3.02:1 */
.ahimsa-day .ah-surface--saffron {
  background: var(--gradient-saffron);
  --text-heading:   #1a1310;
  --text-body:      #1a1310c0;
  --text-secondary: #1a1310c0;
  --text-muted:     #1a131090;
  --border-control: #1a1310aa;
}

/* turmeric — worst stop #d4a02b; heading 7.74:1, body/secondary 4.51:1, muted 3.03:1 */
.ahimsa-day .ah-surface--turmeric {
  background: var(--gradient-turmeric);
  --text-heading:   #1a1310;
  --text-body:      #1a1310b7;
  --text-secondary: #1a1310b7;
  --text-muted:     #1a13108b;
  --border-control: #1a1310a4;
}

/* clay — SURFACE ONLY. Stop #b96144 cannot carry AA text at any
   opacity (best possible 4.24:1, with the most present ink the
   system allows). Use it as a backdrop and put text in an opaque .ah-card. */
.ahimsa-day .ah-surface--clay { background: var(--gradient-clay); }

/* bodhi — worst stop #729e53; heading 5.88:1, body/secondary 4.53:1, muted 3.02:1 */
.ahimsa-day .ah-surface--bodhi {
  background: var(--gradient-bodhi);
  --text-heading:   #1a1310;
  --text-body:      #1a1310d0;
  --text-secondary: #1a1310d0;
  --text-muted:     #1a131099;
  --border-control: #1a1310b3;
}

/* peacock — worst stop #3b93a3; heading 5.14:1, body/secondary 4.51:1, muted 3.01:1 */
.ahimsa-day .ah-surface--peacock {
  background: var(--gradient-peacock);
  --text-heading:   #1a1310;
  --text-body:      #1a1310e1;
  --text-secondary: #1a1310e1;
  --text-muted:     #1a1310a1;
  --border-control: #1a1310bb;
}

/* indigo — SURFACE ONLY. Stop #545fae cannot carry AA text at any
   opacity (best possible 3.17:1, with the most present ink the
   system allows). Use it as a backdrop and put text in an opaque .ah-card. */
.ahimsa-day .ah-surface--indigo { background: var(--gradient-indigo); }

/* monsoon — SURFACE ONLY. Stop #5d7492 cannot carry AA text at any
   opacity (best possible 3.83:1, with the most present ink the
   system allows). Use it as a backdrop and put text in an opaque .ah-card. */
.ahimsa-day .ah-surface--monsoon { background: var(--gradient-monsoon); }

/* amber — worst stop #d4a574; heading 8.24:1, body/secondary 4.51:1, muted 3.01:1 */
.ahimsa-day .ah-surface--amber {
  background: var(--gradient-amber);
  --text-heading:   #1a1310;
  --text-body:      #1a1310b3;
  --text-secondary: #1a1310b3;
  --text-muted:     #1a131088;
  --border-control: #1a1310a2;
}

/* rose — worst stop #e08591; heading 6.93:1, body/secondary 4.53:1, muted 3.02:1 */
.ahimsa-day .ah-surface--rose {
  background: var(--gradient-rose);
  --text-heading:   #1a1310;
  --text-body:      #1a1310c0;
  --text-secondary: #1a1310c0;
  --text-muted:     #1a131090;
  --border-control: #1a1310aa;
}

/* coral — worst stop #e88b6a; heading 7.28:1, body/secondary 4.53:1, muted 3.00:1 */
.ahimsa-day .ah-surface--coral {
  background: var(--gradient-coral);
  --text-heading:   #1a1310;
  --text-body:      #1a1310bc;
  --text-secondary: #1a1310bc;
  --text-muted:     #1a13108d;
  --border-control: #1a1310a7;
}

/* terracotta — worst stop #b87859; heading 5.13:1, body/secondary 4.50:1, muted 3.02:1 */
.ahimsa-day .ah-surface--terracotta {
  background: var(--gradient-terracotta);
  --text-heading:   #1a1310;
  --text-body:      #1a1310e3;
  --text-secondary: #1a1310e3;
  --text-muted:     #1a1310a4;
  --border-control: #1a1310be;
}

/* sage — worst stop #7a9d7f; heading 6.08:1, body/secondary 4.51:1, muted 3.01:1 */
.ahimsa-day .ah-surface--sage {
  background: var(--gradient-sage);
  --text-heading:   #1a1310;
  --text-body:      #1a1310cc;
  --text-secondary: #1a1310cc;
  --text-muted:     #1a131097;
  --border-control: #1a1310b1;
}

/* teal — worst stop #5db8a8; heading 7.76:1, body/secondary 4.53:1, muted 3.00:1 */
.ahimsa-day .ah-surface--teal {
  background: var(--gradient-teal);
  --text-heading:   #1a1310;
  --text-body:      #1a1310b6;
  --text-secondary: #1a1310b6;
  --text-muted:     #1a131089;
  --border-control: #1a1310a3;
}

/* violet — worst stop #a692c4; heading 6.58:1, body/secondary 4.50:1, muted 3.02:1 */
.ahimsa-day .ah-surface--violet {
  background: var(--gradient-violet);
  --text-heading:   #1a1310;
  --text-body:      #1a1310c4;
  --text-secondary: #1a1310c4;
  --text-muted:     #1a131093;
  --border-control: #1a1310ac;
}

/* slate — worst stop #6781a3; heading 4.58:1, body/secondary 4.52:1, muted 3.00:1 */
.ahimsa-day .ah-surface--slate {
  background: var(--gradient-slate);
  --text-heading:   #1a1310;
  --text-body:      #1a1310fb;
  --text-secondary: #1a1310fb;
  --text-muted:     #1a1310ad;
  --border-control: #1a1310c7;
}

/* periwinkle — SURFACE ONLY. Stop #6571b8 cannot carry AA text at any
   opacity (best possible 4.03:1, with the most present ink the
   system allows). Use it as a backdrop and put text in an opaque .ah-card. */
.ahimsa-day .ah-surface--periwinkle { background: var(--gradient-periwinkle); }

/* periwinkle-soft — SURFACE ONLY. Stop #6571b8 cannot carry AA text at any
   opacity (best possible 4.03:1, with the most present ink the
   system allows). Use it as a backdrop and put text in an opaque .ah-card. */
.ahimsa-day .ah-surface--periwinkle-soft { background: var(--gradient-periwinkle-soft); }

/* mauve — worst stop #c4799a; heading 5.72:1, body/secondary 4.50:1, muted 3.01:1 */
.ahimsa-day .ah-surface--mauve {
  background: var(--gradient-mauve);
  --text-heading:   #1a1310;
  --text-body:      #1a1310d3;
  --text-secondary: #1a1310d3;
  --text-muted:     #1a13109b;
  --border-control: #1a1310b4;
}

/* olive — worst stop #7d9963; heading 5.77:1, body/secondary 4.51:1, muted 3.00:1 */
.ahimsa-day .ah-surface--olive {
  background: var(--gradient-olive);
  --text-heading:   #1a1310;
  --text-body:      #1a1310d2;
  --text-secondary: #1a1310d2;
  --text-muted:     #1a13109a;
  --border-control: #1a1310b3;
}

/* ===== tokens/night/palette.css ===== */
/* Ahimsa Night — raw palette.
   Night is built from the cool end of the Day palette. Peacock, indigo,
   monsoon and bodhi carry across unchanged, each gaining one lighter step
   because a dark ground needs brighter ink than a cream one. Two families
   are new: frost, and ember.

   That shared lineage is the whole reason Night reads as the same system
   in a different register rather than as a second, unrelated theme.

   Ahimsa's colour law holds without exception: NEVER PURE BLACK, NEVER
   PURE WHITE. The deepest ground here is #080b10 and the lightest ink
   #eef2f7. And as in Day, there is no red. */

.ahimsa-night {
  /* ---- Neutral: ice -> deep blue-black. 50 is still "lightest"; it is
     the semantic layer that flips which end is ground and which is ink. ---- */
  --neutral-50:  #eef2f7; /* heading ink */
  --neutral-100: #dce3ec; /* body ink */
  --neutral-200: #bcc6d4;
  --neutral-300: #93a0b2;
  --neutral-400: #6d7a8c; /* disabled state, faint text */
  --neutral-500: #4e5a6b;
  --neutral-600: #38424f;
  --neutral-700: #232c37; /* hairline-adjacent wells */
  --neutral-800: #161d26; /* elevated surface */
  --neutral-900: #0e131a; /* canvas */
  --neutral-950: #080b10; /* sunk — the deepest ground in the system */

  /* ---- Carried across from Day, unchanged at 300-700. ---- */
  --peacock-200: #bfe6ea;
  --peacock-300: #8ecbd1;
  --peacock-400: #5fb0bb;
  --peacock-500: #3b93a3;
  --peacock-600: #2c7787;
  --peacock-700: #215d6b;

  --indigo-200: #c7cdec;
  --indigo-300: #9ba4d6;
  --indigo-400: #7480c4;
  --indigo-500: #545fae;
  --indigo-600: #414a92;
  --indigo-700: #313876;

  --monsoon-200: #c6d1de;
  --monsoon-300: #9dadc2;
  --monsoon-400: #7a8fab;
  --monsoon-500: #5d7492;
  --monsoon-600: #475b77;
  --monsoon-700: #36465d;

  --bodhi-200: #d8e8cc;
  --bodhi-300: #b5cf9e;
  --bodhi-400: #93b976;
  --bodhi-500: #729e53;
  --bodhi-600: #5a8340;
  --bodhi-700: #456631;

  /* ---- Frost — pale ice. Night's equivalent of cream: the quietest
     luminous register, and the core colour of every glow. ---- */
  --frost-200: #e4f1f5;
  --frost-300: #c3e2ea;
  --frost-400: #9ccfdb;
  --frost-500: #74b8c8;
  --frost-600: #549cad;
  --frost-700: #3d7c8c;

  /* ---- Ember — warm copper, the only warning hue Night has. Deliberately
     on the warm side so it reads as heat rather than as an alarm. A klaxon
     colour would be a violation; so would red. ---- */
  --ember-200: #f3cdb2;
  --ember-300: #e7ac86;
  --ember-400: #d78c60;
  --ember-500: #c07046;
  --ember-600: #a05935;
  --ember-700: #7e4429;
}

/* ===== tokens/night/gradients.css ===== */
/* Ahimsa Night — atmospheric gradients.
   Still 155 degrees, still four stops, still ONE GRADIENT OWNS A SCREEN.
   Where Day's gradients are a warm wash, Night's climb out of the dark
   toward a single distant light.

   Names are deliberately distinct from Day's, so that no palette-level
   name ever holds two different values across the system. */

.ahimsa-night {
  --gradient-deep:      linear-gradient(155deg, #080b10 0%, #0e131a 50%, #161d26 100%);
  --gradient-horizon:   linear-gradient(155deg, #0e131a 0%, #16222e 40%, #1d3442 75%, #2c5866 100%);
  /* The bright end lands on peacock-600 rather than -500: at -500 the ink
     tops out at 3.17:1 and the gradient could not carry body text. */
  --gradient-aurora:    linear-gradient(155deg, #0e131a 0%, #1a2233 40%, #2b3a5c 75%, #2c7787 100%);
  --gradient-iris:      linear-gradient(155deg, #0e131a 0%, #1c1f38 45%, #313876 80%, #545fae 100%);
  /* monsoon-600 for the same reason: -500 caps the ink at 4.27:1. */
  --gradient-nightfall: linear-gradient(155deg, #0e131a 0%, #1b2530 40%, #2b3849 75%, #475b77 100%);
  --gradient-ember:     linear-gradient(155deg, #0e131a 0%, #241a1a 40%, #47291d 75%, #7e4429 100%);

  /* The one radial in the system, and the ambient layer's ground: a field
     lit faintly from above, falling away to nothing at the edges. */
  --gradient-void: radial-gradient(120% 90% at 50% 0%, #1d2a36 0%, #0e131a 55%, #080b10 100%);
}

/* ===== tokens/night/semantic.css ===== */
/* Ahimsa Night — semantic tokens.
   Exactly the same names Day defines. A component never knows which theme
   it is in. */

.ahimsa-night {
  /* ---- Surfaces ---- */
  --surface-canvas:      var(--neutral-900);
  --surface-canvas-flat: #0e131a;
  --surface-elevated:    var(--neutral-800);
  --surface-sunk:        var(--neutral-950);
  --surface-inverse:     var(--neutral-50);

  /* ---- Text. Quiet steps sit HIGHER here than Day's 65/45/28% — low
     alpha ink disappears against a luminous ground. This is the same
     correction Giraffy already made in the other direction when it
     darkened its quiet text for a warm gradient canvas. ---- */
  --text-heading:   var(--neutral-50);
  --text-body:      var(--neutral-100);
  --text-secondary: #dce3ecb8; /* ~72% */
  --text-muted:     #dce3ec80; /* NON-TEXT ONLY — status dots, rules, disabled marks.
                                   Measures 4.39:1, which clears 3:1 for non-text
                                   (1.4.11) but NOT the 4.5:1 small text owes. No component
                                   sets type in this. */
  --text-faint:     #dce3ec4d; /* ~30% */
  --text-inverse:   var(--neutral-900);

  /* ---- Borders. In Night these are the PRIMARY structural device —
     Day separates with shadow and cream, Night with a hairline.
     A hairline that carries meaning must clear 3:1 (WCAG 1.4.11); use
     --border-strong for those. Subtle and medium are decorative. ---- */
  --border-subtle: #dce3ec1a; /* ~10% — decorative only */
  --border-medium: #dce3ec33; /* ~20% — decorative only */
  --border-strong: #dce3ec59; /* ~35% — emphatic, but DECORATIVE only: 2.69:1 */

  /* The boundary of an interactive control. Owes 3:1 (WCAG 1.4.11). */
  --border-control: #dce3ec8c; /* 55% — 4.86:1 worst case */

  /* ---- Accents ---- */
  --accent-primary:   var(--peacock-300);
  --accent-secondary: var(--indigo-300);
  --accent-success:   var(--bodhi-300);
  --accent-warning:   var(--ember-300); /* the ONLY error colour. Never red. */

  --accent-warning-surface: var(--ember-500); /* fill for a destructive control — warm copper, not red.
                                      A destructive action is still not an emergency. */

  /* Setting SMALL TEXT in an accent? Use the -200 step, not these.
     --accent-success and --accent-warning are tuned as fills and marks and
     clear the 3:1 non-text bar; as 10px type they do not reach 4.5:1. */

  /* ---- Focus ---- */
  --focus-ring:        var(--peacock-300);
  --focus-ring-offset: var(--surface-canvas-flat);
  --focus-ring-width:  2px;

  /* ---- Glass. Tinted cool and much quieter than Day's white: a 42% white
     overlay on a dark ground reads as fog, not as glass. ---- */
  --glass-overlay:        #a9c6d914; /* ~8%  */
  --glass-overlay-strong: #a9c6d926; /* ~15% */
  --glass-scrim:          #05080ca6; /* ~65% — deeper than Day's 45% */
  --glass-blur:           14px;

  /* ---- Glow. Night's answer to Day's paper shadow. Emphasis here is
     light, not lift. ---- */
  --glow-sm: 0 0 12px -2px #8ecbd159;
  --glow-md: 0 0 28px -4px #8ecbd166, 0 0 6px #8ecbd140;
  --glow-lg: 0 0 64px -8px #8ecbd173, 0 0 18px #8ecbd14d;

  /* ---- Night's display type opens up where Day's pulls tight. ---- */
  --tracking-display: 0.03em;
}

/* ===== tokens/night/elevation.css ===== */
/* Ahimsa Night — elevation.
   A drop shadow is invisible on a dark ground, so the same token names do
   a different job: an inset hairline ring establishes the edge, an inner
   top highlight suggests a lit surface, and ambient darkness beneath does
   the lifting. Emphasis comes from --glow-*, never from a brighter fill. */

.ahimsa-night {
  --shadow-xs:    inset 0 0 0 1px #dce3ec14;
  --shadow-sm:    inset 0 0 0 1px #dce3ec1f, 0 1px 0 #ffffff0a;
  --shadow-md:    inset 0 0 0 1px #dce3ec29, inset 0 1px 0 #ffffff14, 0 8px 32px -12px #000000a6;
  --shadow-lg:    inset 0 0 0 1px #dce3ec33, 0 16px 48px -16px #000000b8;
  --shadow-xl:    0 40px 80px -24px #000000cc, 0 0 0 1px #dce3ec1f;
  --shadow-sheet: 0 -16px 48px #000000a6, inset 0 1px 0 #ffffff14;
}

/* ===== tokens/night/type.css ===== */
/* Ahimsa Night — typography. The inversion.
   Night ships NO SERIF. Where Day's two voices are italic serif (feeling)
   and wide-tracked small caps (fact), Night's are ultralight wide-set sans
   (feeling) and mono small caps (measurement).

   Both reference sites speak exactly this language: Uberviz sets its HUD at
   weight 100 / 50px / 2px tracking, and Track sets its single title as
   spaced capitals. There is nothing else in either interface.

   Accessibility constraint, and it is a real one: hairline weights lose
   effective contrast on a dark ground because the stems thin out. Weight
   200 is therefore reserved for display sizes (>= 1.75rem). Body copy is
   400 and never lighter. Ultralight is a gesture for large type, not a
   way to make small text look elegant. */

.ahimsa-night {
  --font-display: "DM Sans", -apple-system, system-ui, sans-serif;
  --font-body:    "DM Sans", -apple-system, system-ui, sans-serif;
  --font-mono:    "DM Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;
}

/* Display — ultralight, opened up. The positive tracking is the whole
   difference from Day, which pulls the same sizes tight. */
.ahimsa-night .ah-display-xl { font-family: var(--font-display); font-size: var(--size-5xl);   font-weight: 200; line-height: 1.02; letter-spacing: 0.04em; }
.ahimsa-night .ah-display-l  { font-family: var(--font-display); font-size: var(--size-4xl);   font-weight: 200; line-height: 1.04; letter-spacing: 0.035em; }
.ahimsa-night .ah-display-m  { font-family: var(--font-display); font-size: var(--size-3xl);   font-weight: 200; line-height: 1.08; letter-spacing: 0.03em; }
.ahimsa-night .ah-heading-l  { font-family: var(--font-display); font-size: var(--size-2xl);   font-weight: 250; line-height: 1.15; letter-spacing: 0.025em; }
.ahimsa-night .ah-heading-m  { font-family: var(--font-display); font-size: var(--size-xl);    font-weight: 300; line-height: 1.2;  letter-spacing: 0.02em; }
.ahimsa-night .ah-title-l    { font-family: var(--font-display); font-size: var(--size-lg);    font-weight: 300; line-height: 1.25; letter-spacing: 0.015em; }
.ahimsa-night .ah-title-m    { font-family: var(--font-display); font-size: var(--size-md);    font-weight: 400; line-height: 1.25; letter-spacing: 0.01em; }

/* Body — 400 and no lighter, at any size. */
.ahimsa-night .ah-body       { font-family: var(--font-body);    font-size: var(--size-base);  font-weight: 400; line-height: 1.45; }
.ahimsa-night .ah-body-serif { font-family: var(--font-body);    font-size: var(--size-md);    font-weight: 400; line-height: 1.6;  letter-spacing: 0.005em; }
.ahimsa-night .ah-label      { font-family: var(--font-body);    font-size: var(--size-sm);    font-weight: 500; line-height: 1.2; }

/* The emotional voice. Day italicises a serif; Night opens a hairline sans
   and tints it toward the light. Still never quotation marks — the setting
   is already doing that job. */
.ahimsa-night .ah-pull-quote {
  font-family: var(--font-display);
  font-size: var(--size-xl);
  font-weight: 200;
  line-height: 1.45;
  letter-spacing: 0.03em;
  color: var(--frost-300);
}

/* The utility voice: mono. This is the Track / Uberviz HUD label exactly. */
.ahimsa-night .ah-caption    { font-family: var(--font-mono); font-size: var(--size-sm);    font-weight: 400; line-height: 1.45; letter-spacing: 0.01em; }
.ahimsa-night .ah-small-caps { font-family: var(--font-mono); font-size: var(--size-xs);    font-weight: 500; line-height: 1.2;  letter-spacing: 0.22em; text-transform: uppercase; }
.ahimsa-night .ah-micro-caps { font-family: var(--font-mono); font-size: var(--size-micro); font-weight: 500; line-height: 1.2;  letter-spacing: 0.32em; text-transform: uppercase; }

.ahimsa-night .ah-display-fluid {
  font-family: var(--font-display);
  font-size: var(--size-fluid-display);
  font-weight: 200;
  line-height: 1.0;
  letter-spacing: var(--tracking-display);
  text-wrap: balance;
}

.ahimsa-night .ah-prose {
  font-family: var(--font-body);
  font-size: var(--size-reading);
  font-weight: 400;
  line-height: var(--leading-relaxed);
  text-wrap: pretty;
}

/* ===== tokens/night/surfaces.css ===== */
/* Ahimsa Night — gradient surfaces. GENERATED by tools/build-surfaces.mjs.
   Do not edit. Change the gradients or the generator instead.

   Each class sets a gradient background AND the ink that is measured to
   work on it. Put one on any element and its contents read correctly:

     <section class="ah-surface--dawn"> ... </section>

   Scoped to the theme, because a Day gradient and a Night gradient need
   different ink and two of them once shared a name. To show a Day surface
   inside a Night page (a specimen, say), wrap it: <div class="ahimsa-day">.

   ONE GRADIENT OWNS A SCREEN. Two of these side by side is the rule this
   system breaks least gladly. */

/* deep — worst stop #161d26; heading 15.09:1, body/secondary 4.53:1, muted 3.04:1 */
.ahimsa-night .ah-surface--deep {
  background: var(--gradient-deep);
  --text-heading:   #eef2f7;
  --text-body:      #eef2f77c;
  --text-secondary: #eef2f77c;
  --text-muted:     #eef2f75c;
  --border-control: #eef2f776;
}

/* horizon — worst stop #2c5866; heading 6.93:1, body/secondary 4.53:1, muted 3.01:1 */
.ahimsa-night .ah-surface--horizon {
  background: var(--gradient-horizon);
  --text-heading:   #eef2f7;
  --text-body:      #eef2f7b9;
  --text-secondary: #eef2f7b9;
  --text-muted:     #eef2f77f;
  --border-control: #eef2f799;
}

/* aurora — worst stop #2c7787; heading 4.56:1, body/secondary 4.52:1, muted 3.01:1 */
.ahimsa-night .ah-surface--aurora {
  background: var(--gradient-aurora);
  --text-heading:   #eef2f7;
  --text-body:      #eef2f7fd;
  --text-secondary: #eef2f7fd;
  --text-muted:     #eef2f7ae;
  --border-control: #eef2f7c8;
}

/* iris — worst stop #545fae; heading 5.15:1, body/secondary 4.51:1, muted 3.01:1 */
.ahimsa-night .ah-surface--iris {
  background: var(--gradient-iris);
  --text-heading:   #eef2f7;
  --text-body:      #eef2f7e5;
  --text-secondary: #eef2f7e5;
  --text-muted:     #eef2f79d;
  --border-control: #eef2f7b7;
}

/* nightfall — worst stop #475b77; heading 6.16:1, body/secondary 4.52:1, muted 3.02:1 */
.ahimsa-night .ah-surface--nightfall {
  background: var(--gradient-nightfall);
  --text-heading:   #eef2f7;
  --text-body:      #eef2f7c8;
  --text-secondary: #eef2f7c8;
  --text-muted:     #eef2f789;
  --border-control: #eef2f7a3;
}

/* ember — worst stop #7e4429; heading 6.80:1, body/secondary 4.51:1, muted 3.02:1 */
.ahimsa-night .ah-surface--ember {
  background: var(--gradient-ember);
  --text-heading:   #eef2f7;
  --text-body:      #eef2f7bc;
  --text-secondary: #eef2f7bc;
  --text-muted:     #eef2f783;
  --border-control: #eef2f79c;
}

/* void — worst stop #1d2a36; heading 13.00:1, body/secondary 4.51:1, muted 3.02:1 */
.ahimsa-night .ah-surface--void {
  background: var(--gradient-void);
  --text-heading:   #eef2f7;
  --text-body:      #eef2f782;
  --text-secondary: #eef2f782;
  --text-muted:     #eef2f75d;
  --border-control: #eef2f777;
}

/* ===== base.css ===== */
/* Ahimsa — base.
   The smallest possible reset, plus the accessibility floor that every
   component inherits. Nothing here is decorative. */

*, *::before, *::after { box-sizing: border-box; }

html {
  /* Never set a px font-size on :root. The whole type scale is in rem
     precisely so that a person's own browser setting still works. */
  -webkit-text-size-adjust: 100%;
}

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  background: var(--surface-canvas-flat);
}

body {
  font-family: var(--font-body);
  font-size: var(--size-reading);
  color: var(--text-body);
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: transparent;
}

button, input, textarea, select { font: inherit; color: inherit; }
strong { font-weight: 600; }

/* Two quick taps on the same spot is a zoom gesture on iOS, and cycling a
   small control is exactly two quick taps on the same small target. Once
   the page is zoomed, an app pinned with position: fixed cannot be scrolled
   at all, which reads as scrolling being broken. Controls opt out of that
   one gesture. PINCH TO ZOOM IS NEVER DISABLED — do not add
   maximum-scale or user-scalable=no to your viewport meta. */
button, [role="button"], [role="tab"], [role="checkbox"] { touch-action: manipulation; }

/* ---- Focus. Visible, always, on every focusable thing.
   The system this was extracted from had no :focus-visible rule anywhere
   and blanket-reset outlines on inputs, which left keyboard users with
   nothing at all. Do not reintroduce `outline: none` without putting
   something equally visible in its place. ---- */
:where(a, button, input, textarea, select, summary, [tabindex]):focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring);
  outline-offset: 2px;
  border-radius: inherit;
}
/* Mouse and touch users don't get a ring; keyboard users always do. */
:where(a, button, input, textarea, select, summary, [tabindex]):focus:not(:focus-visible) {
  outline: none;
}

/* ---- A 44x44 hit area without moving a single painted pixel.
   Small pills and header controls are under the target floor as drawn;
   this grows what the finger hits, not what the eye sees. ---- */
.ah-hit { position: relative; }
.ah-hit::after {
  content: '';
  position: absolute;
  left: 0; right: 0; top: 50%;
  transform: translateY(-50%);
  min-height: 44px;
  height: 100%;
}

/* For a label that must exist for a screen reader and not on screen. */
.ah-visually-hidden {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* ---- Motion. Nothing in this system is load-bearing on animation, so a
   reduced-motion preference removes it outright rather than shortening it.
   Durations are NOT lowered: reduced motion must not mean slower or less
   legible, only stiller. ---- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    scroll-behavior: auto !important;
  }
}

/* ---- Transparency. Five simultaneous blur layers is a rendering cost and
   an accessibility problem; this drops the effect and keeps the surface. ---- */
@media (prefers-reduced-transparency: reduce) {
  .ah-btn--glass, .ah-pill--glass, .ah-chip, .ah-card--glass,
  .ah-mono--glass, .ah-toast > *, .ah-scrim {
    backdrop-filter: none !important;
    -webkit-backdrop-filter: none !important;
  }
}

/* ---- Forced colours (Windows High Contrast and friends).
   This system is built on glass and gradients, and forced-colors discards
   both. Rather than fight it, hand the whole surface over to the user's
   own palette: flat system colours, real borders where a shadow used to
   carry the edge. ---- */
@media (forced-colors: active) {
  .ah-card, .ah-sheet, .ah-pill, .ah-chip, .ah-btn, .ah-mono, .ah-toast > * {
    background: Canvas !important;
    color: CanvasText !important;
    border: 1px solid CanvasText !important;
    box-shadow: none !important;
    backdrop-filter: none !important;
    -webkit-backdrop-filter: none !important;
  }
  .ah-btn { forced-color-adjust: none; background: ButtonFace !important; color: ButtonText !important; border-color: ButtonText !important; }
  .ah-btn:disabled { color: GrayText !important; border-color: GrayText !important; }
  .ah-pill.is-active, .ah-chip.is-selected { background: Highlight !important; color: HighlightText !important; }
  :focus-visible { outline: 2px solid Highlight !important; }
  /* Ambient decoration has nothing to say in a forced palette. */
  .ah-ambient { display: none !important; }
}

/* ===== components/actions.css ===== */
/* Ahimsa components — actions.
   Button, Pill, Link.

   The governing rule for every control here: LEAVING CARRIES THE SAME
   WEIGHT AS CONTINUING. An escape hatch gets a real, equal-metric control,
   never grey text beside a filled pill — grey text reads as a caption, and
   a caption is not a choice. */

/* ================================================================
   Button — the quiet CTA. Wide-tracked uppercase small caps, fully
   rounded, and it never shouts. Press gently shrinks; it never bounces.
   ================================================================ */
.ah-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  border: none;
  border-radius: var(--radius-full);
  cursor: pointer;
  font-family: var(--font-body);
  font-weight: 500;
  text-transform: uppercase;
  line-height: 1.2;
  padding: 14px var(--space-6);
  font-size: var(--size-xs);
  letter-spacing: 0.22em;
  background: var(--text-heading);
  color: var(--text-inverse);
  box-shadow: var(--shadow-sm);
  transition: opacity var(--duration-fast) var(--ease-standard),
              transform var(--duration-fast) var(--ease-standard);
  user-select: none;
  -webkit-user-select: none;
}
/* Press feedback settles, it does not overshoot. And it is :active rather
   than a pointerdown handler, so sliding off the control still cancels the
   action (WCAG 2.5.2). */
.ah-btn:active { transform: scale(0.97); }

.ah-btn--sm { padding: var(--space-2) var(--space-4); font-size: var(--size-micro); }

/* The escape hatch. An OUTLINE, deliberately — see the note at the top. */
.ah-btn--ghost {
  background: transparent;
  color: var(--text-body);
  /* --border-control, not --border-strong: this line is the control's
     edge, so it owes 3:1 non-text contrast. */
  box-shadow: inset 0 0 0 1px var(--border-control);
}

.ah-btn--glass {
  background: var(--glass-overlay);
  color: var(--text-heading);
  backdrop-filter: blur(var(--glass-blur));
  -webkit-backdrop-filter: blur(var(--glass-blur));
  box-shadow: none;
}

/* Destructive. Warm earth in Day, warm copper in Night. Never red:
   removing something is not an emergency, and the colour should not
   suggest the person has done something wrong. */
.ah-btn--danger {
  background: var(--accent-warning-surface);
  color: var(--text-inverse);
  box-shadow: none;
}

.ah-btn--wide { width: 100%; }

.ah-btn:disabled, .ah-btn.is-disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

/* Night lights the primary control rather than lifting it. */
.ahimsa-night .ah-btn { box-shadow: var(--shadow-sm), var(--glow-sm); }
.ahimsa-night .ah-btn--ghost,
.ahimsa-night .ah-btn--glass,
.ahimsa-night .ah-btn--danger { box-shadow: inset 0 0 0 1px var(--border-control); }

/* ================================================================
   Pill — the workhorse. Tags, switchers, filters, quiet actions.
   ================================================================ */
.ah-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px var(--space-3);
  border: none;
  border-radius: var(--radius-full);
  font-family: var(--font-body);
  font-size: var(--size-xs);
  font-weight: 500;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  line-height: 1.2;
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
  background: var(--surface-elevated);
  color: var(--text-body);
  box-shadow: var(--shadow-xs);
  white-space: nowrap;
  transition: background var(--duration-fast) var(--ease-standard),
              color var(--duration-fast) var(--ease-standard);
}
.ah-pill.is-active { background: var(--text-heading); color: var(--text-inverse); }
.ah-pill--outline {
  background: transparent;
  color: var(--text-body);
  box-shadow: inset 0 0 0 1px var(--border-control);
}
.ah-pill--outline.is-active { background: var(--text-heading); color: var(--text-inverse); box-shadow: none; }
.ah-pill--glass {
  background: var(--glass-overlay);
  color: var(--text-heading);
  backdrop-filter: blur(var(--glass-blur));
  -webkit-backdrop-filter: blur(var(--glass-blur));
  box-shadow: none;
}
.ah-pill--glass.is-active { background: var(--text-heading); color: var(--text-inverse); }
/* Not a control: no pointer, and it must not be a <button>. */
.ah-pill--static { cursor: default; }

/* A status dot. It never stands alone — the pill's own text always says the
   same thing, because colour is not a channel some people have
   (WCAG 1.4.1). */
.ah-pill__dot {
  width: 6px;
  height: 6px;
  border-radius: var(--radius-full);
  flex-shrink: 0;
  background: var(--accent-primary);
}

/* ================================================================
   Link — the growing underline. Quiet at rest, and the rule draws itself
   in on hover and focus rather than the text changing colour and jumping.
   ================================================================ */
.ah-link {
  color: inherit;
  text-decoration: none;
  position: relative;
  display: inline;
  padding-bottom: var(--underline-gap);
}
.ah-link::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: var(--underline-thickness);
  background: currentColor;
  opacity: 0.32;
  transform: scaleX(1);
  transform-origin: left center;
  transition: opacity var(--underline-duration) var(--underline-ease),
              transform var(--underline-duration) var(--underline-ease);
}
.ah-link:hover::after,
.ah-link:focus-visible::after { opacity: 1; }

/* A link whose rule is absent until asked for. Use inside running prose
   where a permanent underline on every reference would be noise. */
.ah-link--quiet::after { transform: scaleX(0); opacity: 1; }
.ah-link--quiet:hover::after,
.ah-link--quiet:focus-visible::after { transform: scaleX(1); }

/* ---- An equal-weight pair of choices. The metrics are identical on
   purpose: this container is the shape of the principle. ---- */
.ah-actions {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
}
.ah-actions--stack { flex-direction: column; align-items: stretch; }

/* ===== components/forms.css ===== */
/* Ahimsa components — forms.
   Field, ToggleChip.

   Inputs have no box. A single bottom rule that strengthens on focus does
   the whole job: a full border around a text field is a container, and a
   container implies something being collected. */

/* ================================================================
   Field
   ================================================================ */
.ah-field { display: block; width: 100%; }

.ah-field__label {
  display: block;
  font-family: var(--font-body);
  font-size: var(--size-xs);
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--text-secondary); /* small text owes 4.5:1 — see .ah-caps */
  margin-bottom: 6px;
}

.ah-field input,
.ah-field textarea {
  width: 100%;
  background: transparent;
  border: none;
  padding: var(--space-2) 0;
  resize: vertical;
  line-height: 1.4;
  border-bottom: 1px solid var(--border-medium);
  transition: border-color var(--duration-base) var(--ease-standard);
  font-family: var(--font-body);
  /* 16px, not the 13px this was extracted at. Safari on iOS zooms the
     viewport when a field under 16px takes focus, and a zoomed
     position: fixed app cannot be scrolled — which reads as the app
     breaking the moment you try to type. */
  font-size: var(--size-reading);
  font-weight: 400;
  color: var(--text-body);
  border-radius: 0;
}
.ah-field input:focus,
.ah-field textarea:focus {
  border-bottom-color: var(--border-strong);
}
/* The bottom rule is reinforcement, not the focus indicator: a keyboard
   user gets the real ring from base.css as well. A 25%-to-35% alpha shift
   is not a visible focus state on its own. */
.ah-field input:focus-visible,
.ah-field textarea:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring);
  outline-offset: 3px;
}

/* The large, reflective variant — for the one answer that matters on a
   screen. Italic serif in Day; Night has no serif, so it opens up instead. */
.ah-field--essence input,
.ah-field--essence textarea {
  font-family: var(--font-display);
  font-style: italic;
  font-size: var(--size-lg);
  color: var(--text-heading);
}
.ahimsa-night .ah-field--essence input,
.ahimsa-night .ah-field--essence textarea {
  font-style: normal;
  font-weight: 300;
  letter-spacing: 0.02em;
}

/* --text-secondary, not --text-faint: a placeholder is text, and text
   owes 4.5:1. The visible label above it is what actually carries the
   field's meaning, but an unreadable hint is still an unreadable hint. */
.ah-field input::placeholder,
.ah-field textarea::placeholder {
  color: var(--text-secondary);
  font-style: italic;
  font-family: var(--font-display);
}
.ahimsa-night .ah-field input::placeholder,
.ahimsa-night .ah-field textarea::placeholder {
  font-style: normal;
  font-family: var(--font-body);
}

.ah-field__help {
  display: block;
  margin-top: 6px;
  color: var(--text-secondary);
  font-size: var(--size-base);
}

/* ================================================================
   ToggleChip — multi-select, in the emotional voice. Fills dark when
   chosen. Nothing is pre-selected and nothing is required.
   ================================================================ */
.ah-chip {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: var(--space-2) var(--space-4);
  border: none;
  border-radius: var(--radius-full);
  font-family: var(--font-display);
  font-style: italic;
  font-size: var(--size-md);
  font-weight: 400;
  line-height: 1.2;
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
  background: var(--glass-overlay);
  color: var(--text-heading);
  backdrop-filter: blur(var(--glass-blur));
  -webkit-backdrop-filter: blur(var(--glass-blur));
  box-shadow: inset 0 0 0 1px var(--border-control);
  transition: background var(--duration-base) var(--ease-standard),
              color var(--duration-base) var(--ease-standard);
}
.ah-chip.is-selected {
  background: var(--text-heading);
  color: var(--text-inverse);
  box-shadow: none;
}
.ah-chip__dot { width: 7px; height: 7px; border-radius: var(--radius-full); flex-shrink: 0; }

.ahimsa-night .ah-chip {
  font-style: normal;
  font-weight: 300;
  letter-spacing: 0.02em;
  box-shadow: inset 0 0 0 1px var(--border-control);
}
.ahimsa-night .ah-chip.is-selected { box-shadow: var(--glow-sm); }

/* ===== components/surfaces.css ===== */
/* Ahimsa components — surfaces.
   Card, Sheet.

   Three surface levels plus one glass level, expressed as background FIRST
   and shadow second. Sunk surfaces get no shadow at all; the colour is the
   elevation. No coloured-left-border cards, no hard outlines. */

/* ================================================================
   Card
   ================================================================ */
.ah-card {
  position: relative;
  border-radius: var(--radius-xl);
  padding: var(--space-6);
  overflow: hidden;
  background: var(--surface-elevated);
  color: var(--text-body);
  box-shadow: var(--shadow-md);
  flex-shrink: 0;
}
.ah-card--list { box-shadow: var(--shadow-sm); }
.ah-card--sunk { background: var(--surface-sunk); box-shadow: none; }
.ah-card--glass {
  background: var(--glass-overlay);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  box-shadow: none;
}
/* A gradient card is the one featured moment on a screen. Set the gradient
   inline or with your own class: --gradient-dawn, --gradient-aurora, etc.
   Two gradient cards side by side is the rule this system breaks least
   gladly. */
.ah-card--gradient {
  background: var(--gradient-dawn);
  color: var(--text-heading);
  box-shadow: var(--shadow-lg);
}
.ah-card--flat { box-shadow: none; }
.ah-card--p0 { padding: 0; }

.ah-card.is-tappable { cursor: pointer; }
/* Gentler than a button: a whole card moving 3% would read as a lurch. */
.ah-card.is-tappable:active { transform: scale(0.99); }

.ahimsa-night .ah-card--gradient { box-shadow: var(--shadow-lg), var(--glow-md); }

/* ================================================================
   Sheet — the bottom sheet, and the only modal this system has.

   THREE WAYS OUT, ALWAYS: the scrim, the grab handle, and a visible Close.
   Never a trap. The Close is a real surface at equal weight, not a ghost —
   leaving is as legitimate as staying.
   ================================================================ */
.ah-scrim {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  background: var(--glass-scrim);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
  z-index: 50;
}

.ah-sheet {
  width: 100%;
  max-height: 88%;
  overflow-y: auto;
  background: var(--surface-elevated);
  border-top-left-radius: var(--radius-3xl);
  border-top-right-radius: var(--radius-3xl);
  box-shadow: var(--shadow-sheet);
  padding: var(--space-3) var(--space-6) calc(var(--space-7) + var(--safe-bottom, 0px));
  animation: ah-sheet-up var(--duration-slower) var(--ease-decelerate);
}

.ah-sheet__grab {
  width: 40px;
  height: 4px;
  border-radius: var(--radius-full);
  background: var(--border-strong);
  margin: 0 auto var(--space-5);
}

.ah-sheet__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--space-4);
  gap: var(--space-3);
}
.ah-sheet__title {
  font-family: var(--font-display);
  font-size: var(--size-xl);
  color: var(--text-heading);
  line-height: 1.15;
  margin: 0;
}
.ah-sheet__close {
  border: none;
  background: var(--surface-sunk);
  color: var(--text-secondary);
  width: 44px;
  height: 44px;
  border-radius: var(--radius-full);
  cursor: pointer;
  font-size: var(--size-lg);
  line-height: 1;
  flex-shrink: 0;
}

@keyframes ah-sheet-up {
  from { transform: translateY(100%); }
  to   { transform: translateY(0); }
}

.ahimsa-night .ah-sheet { border-top: 1px solid var(--border-medium); }

/* ===== components/display.css ===== */
/* Ahimsa components — display.
   SmallCapsLabel, PullQuote, Monogram, ProgressDots, StatusDot, SectionRule.

   This system is deliberately icon-light, and its most distinctive "icon"
   is the absence of one: THERE IS NO NOTIFICATION BADGE COMPONENT, by
   design. Updates surface through the rhythm of use, never a red dot. */

/* ================================================================
   SmallCapsLabel — the utility voice. Wide-tracked uppercase, optionally
   led by a short rule, and it reads like a magazine caption.
   ================================================================ */
.ah-caps {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-family: var(--font-body);
  font-size: var(--size-xs);
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  line-height: 1.2;
  /* --text-secondary, not --text-muted. This is 10px uppercase, which is
     SMALL text and owes 4.5:1 — the quieter token measures 3.35:1 and
     fails. Nothing in this system sets small text in --text-muted. */
  color: var(--text-secondary);
}
.ah-caps--micro { font-size: var(--size-micro); letter-spacing: 0.32em; }
.ah-caps--rule::before {
  content: '';
  width: 24px;
  height: 1px;
  background: currentColor;
  opacity: 0.6;
  flex-shrink: 0;
}
/* In Night the utility voice is mono — the HUD label. */
.ahimsa-night .ah-caps { font-family: var(--font-mono); }

/* ================================================================
   PullQuote — the emotional voice, and the system's signature.
   NEVER quotation marks: the setting is already the quotation, and the
   marks would only shout.
   ================================================================ */
.ah-quote {
  margin: 0;
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 400;
  font-size: var(--size-lg);
  line-height: 1.35;
  color: var(--text-heading);
  text-wrap: pretty;
  text-align: center;
}
.ah-quote--md { font-size: var(--size-md); }
.ah-quote--xl { font-size: var(--size-xl); }
.ah-quote--bordered {
  text-align: left;
  border-left: 1px solid var(--border-medium);
  padding-left: var(--space-4);
}
.ahimsa-night .ah-quote {
  font-style: normal;
  font-weight: 200;
  letter-spacing: 0.03em;
  line-height: 1.45;
  color: var(--frost-300);
}

/* ================================================================
   Monogram — a circular avatar that is an initial, not a photograph.
   Set --ah-mono-size (default 56px); the letter scales with the box.
   ================================================================ */
.ah-mono {
  --ah-mono-size: 56px;
  width: var(--ah-mono-size);
  height: var(--ah-mono-size);
  font-size: calc(var(--ah-mono-size) * 0.62);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-full);
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 300;
  line-height: 1;
  flex-shrink: 0;
  background: var(--surface-elevated);
  color: var(--text-heading);
  box-shadow: var(--shadow-sm);
}
.ah-mono--sunk { background: var(--surface-sunk); color: var(--text-secondary); box-shadow: none; }
.ah-mono--glass {
  background: var(--glass-overlay);
  backdrop-filter: blur(var(--glass-blur));
  -webkit-backdrop-filter: blur(var(--glass-blur));
  box-shadow: inset 0 0 0 1px var(--glass-overlay-strong);
}
.ah-mono--s20  { --ah-mono-size: 20px; }
.ah-mono--s44  { --ah-mono-size: 44px; }
.ah-mono--s84  { --ah-mono-size: 84px; }
.ah-mono--s110 { --ah-mono-size: 110px; }
.ah-mono--s144 { --ah-mono-size: 144px; }
/* Night has no serif, so the monogram becomes a hairline ring around an
   ultralight letter, lit rather than lifted. */
.ahimsa-night .ah-mono {
  font-style: normal;
  font-weight: 200;
  letter-spacing: 0.02em;
  box-shadow: inset 0 0 0 1px var(--border-control), var(--glow-sm);
}
.ahimsa-night .ah-mono--sunk { box-shadow: inset 0 0 0 1px var(--border-medium); }

/* ================================================================
   ProgressDots — where you are, with no count and no urgency.
   Each dot is a real button: pagination you can only reach by dragging
   is not reachable (WCAG 2.5.7).
   ================================================================ */
.ah-dots { display: inline-flex; align-items: center; gap: 2px; }
/* The control is a real 44x44 box; the dot is painted inside it. An
   invisible ::after hit area works for a finger but is invisible to an
   audit, and "it is accessible, trust me" is not a position this system
   takes. */
.ah-dots button,
.ah-dots span {
  width: 44px;
  height: 44px;
  padding: 0;
  border: none;
  background: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.ah-dots button { cursor: pointer; }
.ah-dots button::before,
.ah-dots span::before {
  content: '';
  display: block;
  width: 5px;
  height: 5px;
  border-radius: var(--radius-full);
  background: var(--border-strong);
  opacity: 0.5;
  transition: width var(--duration-fast) var(--ease-standard),
              opacity var(--duration-fast) var(--ease-standard);
}
.ah-dots .is-on::before { width: 22px; opacity: 1; background: var(--text-heading); }

/* ================================================================
   StatusDot — never the only carrier of its meaning. The adjacent text
   says the same thing (WCAG 1.4.1).
   ================================================================ */
.ah-sdot {
  width: 7px;
  height: 7px;
  border-radius: var(--radius-full);
  flex-shrink: 0;
  display: inline-block;
  background: var(--text-muted);
}

/* ================================================================
   SectionRule — the signature "── label ──────".
   ================================================================ */
.ah-rule-row { display: flex; align-items: center; gap: var(--space-2); }
.ah-rule-row::before {
  content: '';
  width: 16px;
  border-top: 1px solid var(--border-medium);
  flex-shrink: 0;
}
.ah-rule-row::after {
  content: '';
  flex: 1;
  border-top: 1px solid var(--border-medium);
}

/* ===== components/feedback.css ===== */
/* Ahimsa components — feedback.
   Toast, Notice.

   This is the file where the attention principle is load-bearing, so read
   this before adding anything to it:

   NOTHING IN AN AHIMSA INTERFACE APPEARS ON ITS OWN. The test is not "is it
   dismissible" but DID THE PERSON CAUSE THIS, HERE, NOW?

   There is deliberately no UpdateBar, no InstallBar, no banner, no modal
   and no badge in this system. A floating bar that slides in to announce
   something the person did not ask about spends their attention on the
   product's behalf, and whatever they were doing is what they should still
   be doing. Version news belongs in Settings, behind a control they press.
   See patterns/version-check. */

/* ================================================================
   Toast — the acknowledgement of an action just taken. That is its ONLY
   legitimate use. It never carries news, never announces an arrival, and
   never appears on a timer the person did not start.

   Always role="status" with aria-live="polite". NEVER role="alert" or
   aria-live="assertive": an assertive region interrupts a screen-reader
   user mid-sentence, which is the same harm in a different channel.
   ================================================================ */
.ah-toast {
  position: absolute;
  left: 0;
  right: 0;
  bottom: calc(var(--safe-bottom, 0px) + var(--space-6));
  z-index: 60;
  display: flex;
  justify-content: center;
  /* It cannot be in the way of anything, because it was not invited to be. */
  pointer-events: none;
  padding: 0 var(--space-5);
}
.ah-toast > * {
  background: var(--glass-overlay-strong);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border-radius: var(--radius-full);
  padding: var(--space-2) 18px;
  box-shadow: var(--shadow-md);
  max-width: 320px;
  color: var(--text-heading);
  animation: ah-rise var(--duration-base) var(--ease-standard);
}
.ahimsa-night .ah-toast > * {
  box-shadow: var(--shadow-md), var(--glow-sm);
}

@keyframes ah-rise {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ================================================================
   Notice — the replacement for every banner this system refuses.
   It sits IN the document flow, where the person already is, and it is
   rendered because they navigated to it or because they just acted. It
   does not float, does not cover anything, and does not take focus.
   ================================================================ */
.ah-notice {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-lg);
  background: var(--surface-sunk);
  color: var(--text-body);
  font-size: var(--size-base);
}
.ah-notice__title {
  font-family: var(--font-body);
  font-size: var(--size-xs);
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--text-secondary); /* small text owes 4.5:1 — see .ah-caps */
}
.ahimsa-night .ah-notice__title { font-family: var(--font-mono); }

/* Something did not work. Stated in the same voice as everything else:
   a warm tint and a sentence, not a red box and an exclamation mark.
   The person has not done anything wrong. */
.ah-notice--warning {
  background: color-mix(in srgb, var(--accent-warning) 14%, var(--surface-elevated));
  color: var(--text-body);
}
.ah-notice--warning .ah-notice__title { color: var(--accent-warning); }

.ah-notice__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}

/* ===== patterns/shell.css ===== */
/* Ahimsa patterns — shells.
   Two of them, and picking the right one matters.

   .ah-page — a normal document. Fluid gutters, a reading measure, scrolls
     the way the browser intends. Use this for sites, docs, anything read.

   .ah-app — a fixed full-viewport application frame with safe-area insets,
     widening in steps on larger screens so a desktop window gets a
     comfortable app rather than a narrow strip. Use this for installed
     PWAs. It is a deliberate trade: a fixed shell cannot reflow the way a
     document can, so do not reach for it to hold an article. */

/* ================================================================
   Page shell
   ================================================================ */
.ah-page {
  max-width: 1440px;
  margin: 0 auto;
  padding-inline: var(--gutter-fluid);
  background: var(--surface-canvas);
}
/* The reading measure. Long lines are a readability problem long before
   they are an aesthetic one. */
.ah-measure { max-width: 68ch; }
.ah-measure--wide { max-width: 88ch; }

.ah-stack { display: flex; flex-direction: column; gap: var(--space-4); }
.ah-stack--tight { gap: var(--space-2); }
.ah-stack--loose { gap: var(--space-8); }
/* Magazine composition: at least one full unit of air above and below a
   heading. If a section feels crowded, remove elements before shrinking
   type. */
.ah-section { padding-block: var(--space-16); }

/* ================================================================
   App shell
   ================================================================ */
.ah-app {
  position: fixed;
  inset: 0;
  overflow: hidden;
  background: var(--surface-canvas);
  --gutter: 20px;
  --safe-top: env(safe-area-inset-top, 0px);
  --safe-bottom: env(safe-area-inset-bottom, 0px);
  --safe-left: env(safe-area-inset-left, 0px);
  --safe-right: env(safe-area-inset-right, 0px);
  --top-pad: calc(var(--safe-top) + 14px);
  --scroll-tail: calc(var(--safe-bottom) + var(--space-12));
}
/* Requires <meta name="viewport" content="width=device-width,
   initial-scale=1, viewport-fit=cover"> for env(safe-area-inset-*) to be
   live. Note what is NOT in that meta: no maximum-scale, no
   user-scalable=no. Pinch zoom stays. */

@media (min-width: 560px) {
  .ah-app {
    max-width: 480px;
    left: 50%;
    transform: translateX(-50%);
    box-shadow: var(--shadow-xl);
  }
}
@media (min-width: 900px)  { .ah-app { max-width: 720px; --gutter: 32px; } }
@media (min-width: 1200px) { .ah-app { max-width: 900px; --gutter: 40px; } }

.ah-screen {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  padding-top: var(--top-pad);
  background: var(--surface-canvas);
}
.ah-screen > *:not(.ah-scroll) { flex-shrink: 0; }
.ah-screen--over { z-index: 6; }

.ah-scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  /* y only. Setting this on x swallows horizontal trackpad swipes and can
     turn a pager into a one-way trap with no pointer route back. */
  overscroll-behavior-y: contain;
  padding: 6px var(--gutter) 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
/* A spacer BLOCK, not bottom padding. End padding on a flex scroll
   container is not reliably part of what the browser will scroll to; a
   block is content, and every engine scrolls to the end of its content. */
.ah-scroll::after {
  content: '';
  display: block;
  flex: 0 0 auto;
  height: var(--scroll-tail, 0px);
}

/* Header row */
.ah-hdr {
  padding: 6px var(--gutter) var(--space-2);
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-shrink: 0;
}

/* A horizontal row of chips that fades at the edge instead of ending
   abruptly, and wraps rather than scrolling once there is room. A
   horizontal scroller is a drag-only interaction, so pair it with the
   same choices reachable another way. */
.ah-hrow {
  display: flex;
  gap: var(--space-2);
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  padding-inline: var(--gutter);
  mask-image: linear-gradient(90deg, #000 calc(100% - 18px), transparent);
  -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 18px), transparent);
}
@media (min-width: 900px) {
  .ah-hrow { flex-wrap: wrap; overflow-x: visible; mask-image: none; -webkit-mask-image: none; }
}

/* A list of self-contained rows: one column on a phone, two when the
   window allows it. */
.ah-list { display: flex; flex-direction: column; gap: var(--space-2); }
@media (min-width: 900px) {
  .ah-list { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* A sticky footer over a gradient uses strong glass rather than restarting
   the gradient, which would read as a seam. */
.ah-footer {
  flex-shrink: 0;
  padding: var(--space-3) var(--gutter) calc(var(--space-5) + var(--safe-bottom, 0px));
  background: var(--glass-overlay-strong);
  backdrop-filter: blur(var(--glass-blur));
  -webkit-backdrop-filter: blur(var(--glass-blur));
  border-top: 1px solid var(--border-subtle);
}

/* Opt-in, and only inside an app shell: hiding scrollbars removes a
   pointer target that some people rely on, so it is never the default. */
.ah-scroll--bare { scrollbar-width: none; }
.ah-scroll--bare::-webkit-scrollbar { display: none; }

/* ===== ambient/ambient.css ===== */
/* Ahimsa Night — the ambient layer.
   =====================================================================
   WHAT THIS IS
   A decorative field that sits behind a screen and does nothing else. It
   is the Night theme's answer to Day's cream paper: atmosphere, not
   information.

   The reference implementations (littleworkshop's TRACK, uberviz's Splice)
   reach this look through three.js with a postprocessing chain — FXAA,
   RGBShift, BarrelBlur, HueSaturation, a scanline pass, Shake, and additive
   glow over procedural geometry. Every one of those has a faithful CSS or
   SVG equivalent, so none of it needs WebGL, a canvas, or a single byte of
   JavaScript:

     additive glow   -> mix-blend-mode: plus-lighter (literally additive)
     SimplexNoise    -> feTurbulence fractalNoise, as a data-URI image
     Lines shader    -> repeating-linear-gradient
     BarrelBlur      -> radial-gradient falloff
     HueSaturation   -> filter: hue-rotate on the ambient layer only
     RGBShift        -> per-channel feOffset (see aberration.svg)
     geometry        -> repeating gradients and SVG line motifs

   WHAT IT MUST NEVER DO
   This layer is where a futuristic register usually starts doing harm, so
   the constraints are hard, not stylistic:

     - It is aria-hidden and purely decorative. It NEVER encodes state,
       progress, or anything a person could miss by ignoring it.
     - It never reacts to error. A glitch effect that fires when something
       goes wrong tells someone their screen is broken.
     - It never flashes. Nothing here pulses faster than 0.1Hz, and no
       animation is shorter than 2s — well outside WCAG 2.3.1's three-flash
       threshold.
     - It animates opacity and hue only. Never transform, never position:
       movement in peripheral vision is a demand for attention.
     - It disappears entirely under prefers-reduced-motion,
       prefers-reduced-transparency, and forced-colors.

   Spectacle that asks for nothing is the only kind this system allows.
   ===================================================================== */

.ahimsa-night {
  /* Tuning knobs. These are the four values worth changing; everything
     else in this file is derived from them. */
  --ambient-grain:    0.05;
  --ambient-scan:     0.035;
  --ambient-bloom:    0.5;
  --ambient-lines:    0.1;
  --ambient-vignette: 0.75;
  --ambient-drift:    32s;
}

.ah-ambient {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  /* Decorative. Always paired with aria-hidden="true" in the markup. */
}

/* Content sits above the field. */
.ah-ambient ~ * { position: relative; z-index: 1; }

/* ---- The ground. One radial, lit faintly from above, falling away to
   almost nothing at the edges. ---- */
.ah-ambient--void { background: var(--gradient-void); }

/* ---- Bloom. Additive light, stacked. plus-lighter is real additive
   blending, which is exactly what the reference does in a shader. ---- */
.ah-ambient--bloom::before {
  content: '';
  position: absolute;
  inset: -20%;
  mix-blend-mode: plus-lighter;
  opacity: var(--ambient-bloom);
  background:
    radial-gradient(38% 28% at 22% 18%, #3b93a340 0%, transparent 70%),
    radial-gradient(30% 24% at 78% 32%, #545fae33 0%, transparent 72%),
    radial-gradient(46% 34% at 50% 96%, #74b8c826 0%, transparent 76%);
  animation: ah-ambient-breathe var(--ambient-drift) var(--ease-standard) infinite alternate;
}

/* ---- Grain. feTurbulence, desaturated, as a data-URI image. Soft-light
   keeps it from lifting the black point. ---- */
.ah-ambient--grain::after {
  content: '';
  position: absolute;
  inset: 0;
  mix-blend-mode: soft-light;
  opacity: var(--ambient-grain);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 180px 180px;
}

/* ---- Scanlines. The Lines shader, at a 3px pitch and a whisper of
   opacity. Any stronger and it becomes a texture, which this system
   does not do. ---- */
.ah-ambient--scan {
  background-image: repeating-linear-gradient(
    0deg,
    transparent 0 2px,
    #dce3ec 2px 3px
  );
  background-blend-mode: overlay;
  opacity: 1;
}
.ah-ambient--scan::before {
  content: '';
  position: absolute;
  inset: 0;
  mix-blend-mode: overlay;
  opacity: var(--ambient-scan);
  background: repeating-linear-gradient(0deg, transparent 0 2px, #dce3ec 2px 3px);
}

/* ---- Vignette. BarrelBlur's falloff, without the blur cost. ---- */
.ah-ambient--vignette {
  box-shadow: inset 0 0 min(40vw, 320px) min(8vw, 64px) #05080c;
}

/* ---- Geometry. Procedural in the reference; here, gradients that read
   the same at rest. TRACK's environment is line and plane, and neither
   needs a mesh to suggest it. ---- */

/* A ruled horizon, receding. */
.ah-ambient--horizon::before {
  content: '';
  position: absolute;
  left: -50%; right: -50%; bottom: 0;
  height: 62%;
  opacity: var(--ambient-lines);
  background:
    repeating-linear-gradient(0deg, transparent 0 38px, #8ecbd1 38px 39px),
    repeating-linear-gradient(90deg, transparent 0 68px, #8ecbd1 68px 69px);
  mask-image: linear-gradient(to top, #000 0%, transparent 92%);
  -webkit-mask-image: linear-gradient(to top, #000 0%, transparent 92%);
  transform: perspective(420px) rotateX(74deg);
  transform-origin: bottom center;
}

/* A flat measured grid — the HUD register rather than the landscape one. */
.ah-ambient--grid::before {
  content: '';
  position: absolute;
  inset: 0;
  opacity: calc(var(--ambient-lines) * 0.7);
  background:
    repeating-linear-gradient(0deg, transparent 0 47px, #9ba4d6 47px 48px),
    repeating-linear-gradient(90deg, transparent 0 47px, #9ba4d6 47px 48px);
  mask-image: radial-gradient(70% 60% at 50% 40%, #000 0%, transparent 100%);
  -webkit-mask-image: radial-gradient(70% 60% at 50% 40%, #000 0%, transparent 100%);
}

/* Concentric isolines — a contour map of nothing in particular. */
.ah-ambient--isolines::before {
  content: '';
  position: absolute;
  inset: -10%;
  opacity: var(--ambient-lines);
  background: repeating-radial-gradient(
    closest-side at 50% 42%,
    transparent 0 42px,
    #74b8c8 42px 43px
  );
  mask-image: radial-gradient(60% 60% at 50% 42%, #000 20%, transparent 100%);
  -webkit-mask-image: radial-gradient(60% 60% at 50% 42%, #000 20%, transparent 100%);
  animation: ah-ambient-hue var(--ambient-drift) var(--ease-standard) infinite alternate;
}

/* ---- Drift. Opacity and hue only, over half a minute, and it never
   reaches an extreme. If you can notice it happening, it is too strong. ---- */
@keyframes ah-ambient-breathe {
  from { opacity: calc(var(--ambient-bloom) * 0.72); }
  to   { opacity: var(--ambient-bloom); }
}
@keyframes ah-ambient-hue {
  from { filter: hue-rotate(-8deg); }
  to   { filter: hue-rotate(8deg); }
}

/* ---- The three ways this layer goes away. ---- */
@media (prefers-reduced-motion: reduce) {
  .ah-ambient,
  .ah-ambient::before,
  .ah-ambient::after { animation: none !important; }
}
@media (prefers-reduced-transparency: reduce) {
  .ah-ambient--grain::after,
  .ah-ambient--scan::before,
  .ah-ambient--bloom::before { display: none; }
}
@media (forced-colors: active) {
  .ah-ambient { display: none !important; }
}
