/* ---------------------------------------------------------------------------
 * CreateSpace — the entry point. theme.mjs names this file, and the build
 * flattens the chain below into a single /ds/styles.css.
 *
 * Order is load-bearing:
 *   fonts        defines --font-display / --font-sans / --font-mono, which
 *                typography.css composes into --type-display and friends
 *   colors       defines the palette and the roles the inverted set remaps
 *   colors-dark  must follow colors, it overrides those roles
 *   geometry     must follow colors-dark: its own [data-theme="dark"] block
 *                restates the shadows, and a shadow cast in the wrong mode is
 *                the one thing a later override cannot fix
 *   base         needs every token above to style the page
 *   components   needs base's reset
 *
 * Import this and nothing else for the full system. If you only want the
 * values — retargeting an app's existing stylesheet, say — import tokens.css
 * instead and keep your own component CSS.
 * ------------------------------------------------------------------------- */


/* ---- tokens/fonts.css ---- */
/* ---------------------------------------------------------------------------
 * CreateSpace's three families, self-hosted.
 *
 * The reasons RawBlock could not take these from a CDN are properties of this
 * repo, not of that design system, so they carry over unchanged:
 *
 *   1. transparency-render runs under Cross-Origin-Embedder-Policy: require-corp
 *      so it can use SharedArrayBuffer for multithreaded ffmpeg. A stylesheet
 *      <link> is a no-cors request, and no-cors subresources are blocked under
 *      require-corp — that one tool would silently lose its typefaces.
 *   2. @import inside @import is a render-blocking waterfall.
 *
 * A third reason used to be listed here: that the privacy page promised no
 * third-party host ever saw a request. That promise was not true — the Google
 * Fonts reference has loaded its specimens from Google since it was written —
 * and it has been removed from the page, so it is removed from here too. The
 * site's own faces are still served from this domain; the two reasons above are
 * why, and they hold on their own.
 *
 * CreateSpace names Poppins for display, DM Sans for body and Fira Code for
 * machine text. The cost against RawBlock is real but small: 94.5 KB of latin
 * against 83 KB, because Poppins is not a variable font and the three weights
 * the spec uses — 600 for h2/h3, 700 for h1, 800 for the hero — are three
 * separate files. DM Sans and Fira Code are variable and are one file each.
 *
 * latin covers English and German; latin-ext adds Czech, Polish and the rest of
 * Central Europe. unicode-range means latin-ext is only fetched if a glyph in
 * it is actually painted, so the common case is five latin files.
 * ------------------------------------------------------------------------- */

@font-face {
  font-family: "DM Sans";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("./fonts/dm-sans-var-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "DM Sans";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("./fonts/dm-sans-var-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Poppins";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("./fonts/poppins-600-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Poppins";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("./fonts/poppins-600-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Poppins";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("./fonts/poppins-700-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Poppins";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("./fonts/poppins-700-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Poppins";
  font-style: normal;
  font-weight: 800;
  font-display: swap;
  src: url("./fonts/poppins-800-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Poppins";
  font-style: normal;
  font-weight: 800;
  font-display: swap;
  src: url("./fonts/poppins-800-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Fira Code";
  font-style: normal;
  font-weight: 400 500;
  font-display: swap;
  src: url("./fonts/fira-code-var-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Fira Code";
  font-style: normal;
  font-weight: 400 500;
  font-display: swap;
  src: url("./fonts/fira-code-var-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
  /* --font-sans is the body face and the name every tool already sets on its
     own :root, so it stays the name rather than becoming --font-body. */
  --font-sans: "DM Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
  --font-display: "Poppins", "Century Gothic", "Futura", sans-serif;
  --font-mono: "Fira Code", ui-monospace, "Courier New", monospace;
  --font-ui: var(--font-sans);
}


/* ---- tokens/colors.css ---- */
/* ---------------------------------------------------------------------------
 * CreateSpace's palette.
 *
 * Three brand hues used as blocks, not as tints. Where RawBlock had one accent
 * (black) and four reserved hues it refused to decorate with, CreateSpace has
 * rose, blue and yellow and expects them to be seen — the spec calls for "bold
 * colour blocks" and a portfolio-first page. The discipline here is not
 * restraint of colour, it is restraint of *role*: each hue still has one job.
 *
 * Four decisions the spec does not contain, and why:
 *
 *   Yellow is fill-only. #FACC15 on white is 1.53:1. It cannot be text, an
 *   icon, or a border on a light ground, ever. As a fill with --ink-1 on it,
 *   it is 11.92:1, and that is the only legal use. There is deliberately no
 *   --ink-on-yellow-inverse or yellow text role for something to reach for.
 *
 *   Green and orange are relit. The spec's #16A34A (3.30:1) and #D97706
 *   (3.19:1) both fail AA as text on white, and six tools show inline success
 *   and warning copy. Darkened to #15803D and #B45309, both 5.02:1, which is
 *   the same relighting RawBlock did to its red for the same reason.
 *
 *   The neutral ramp is invented. No kit on designmd.ai ships one, and
 *   pdf-editor's baseplate maps a fourteen-step scale onto it while
 *   transparency-render builds panels from --grey-700. Unlike RawBlock's —
 *   which is weighted hard to the ends because it has nothing to use a mid
 *   grey for — this one is an even ramp, because a system with real surfaces
 *   and real shadows needs the middle.
 *
 *   The greys carry a warm violet-rose bias rather than being pure neutral, so
 *   a card edge or a hint line sits with the rose instead of reading as a
 *   second, colder system underneath it.
 * ------------------------------------------------------------------------- */

:root {
  /* Core */
  --black: #000000;
  --white: #ffffff;

  /* ---------------------------------------------------------------------
   * The three brand hues, straight from the spec.
   *
   * Rose is the primary: buttons, hero accents, the thing you are meant to
   * press. Blue is links and secondary actions — CreateSpace assigns it both,
   * which is a real divergence from RawBlock's "blue means hyperlink and
   * nothing else" and is taken deliberately, because a system with this much
   * colour cannot also reserve one. Yellow is highlights, badges and
   * callouts, as a block behind dark ink.
   * ------------------------------------------------------------------- */
  --rose:   #e11d48; /* primary actions, hero accents — 4.70:1 under white */
  --blue:   #2563eb; /* hyperlinks and secondary actions — 5.17:1 on white */
  --yellow: #facc15; /* fill only. See the header. */

  /* ---------------------------------------------------------------------
   * The mark's ground, and the one colour in this file that does not adapt.
   *
   * --rose is lifted to #fb7185 on a dark page so it can carry text there.
   * A logo must not do that: white on #fb7185 is 2.1:1, so the wordmark would
   * go illegible in dark mode, and a brand colour that changes with the
   * visitor's theme is two brands. This value is deliberately absent from
   * colors-dark.css. White on it is 4.70:1 in both modes, and theme.mjs
   * promises that pairing so it stays true.
   * ------------------------------------------------------------------- */
  --brand-mark: #e11d48;
  --ink-on-brand-mark: #ffffff;

  /* Status. Relit from the spec so each carries text on white. */
  --green:  #15803d; /* success — spec #16a34a was 3.30:1 */
  --orange: #b45309; /* warning — spec #d97706 was 3.19:1 */
  --red:    #dc2626; /* error and destructive — 4.83:1, unchanged */

  /* ---------------------------------------------------------------------
   * Neutrals — an even ramp with a warm violet bias.
   * ------------------------------------------------------------------- */
  --grey-050: #faf8f9;
  --grey-100: #f4f1f3; /* sunken: the input fill */
  --grey-200: #e8e3e7; /* input hover */
  --grey-300: #d5ced3; /* the hairline that divides things */
  --grey-400: #aba1a8;
  --grey-500: #7e747c;
  --grey-600: #5f5660;
  --grey-700: #443d48;
  --grey-800: #2a2430;
  --grey-900: #171320; /* ink is a warm near-black, not pure black */

  /* ---------------------------------------------------------------------
   * Surfaces.
   *
   * --surface-glass is the frosted panel the spec asks for. It is a
   * translucent white rather than a tint, because the whole point is that a
   * colour block shows through it; the blur lives on the component.
   * ------------------------------------------------------------------- */
  --surface-page: var(--white);
  --surface-card: var(--white);
  --surface-raised: var(--white);
  --surface-sunken: var(--grey-100);
  --surface-inverse: var(--grey-900);
  --surface-glass: rgba(255, 255, 255, 0.72);
  --surface-glass-line: rgba(255, 255, 255, 0.85);
  /* The plate is the inverted section — a header, a hero or a footer. */
  --surface-brand: var(--grey-900);

  /* Ink */
  --ink-1: var(--grey-900);
  --ink-2: var(--grey-700);
  --ink-3: var(--grey-600);
  --ink-on-brand: var(--white);
  --ink-on-brand-2: var(--grey-300);
  --ink-inverse: var(--white);

  /* Lines. Unlike RawBlock, a line here is a quiet grey hairline and weight is
     not carrying hierarchy — radius, fill and shadow are. --line-2 is the
     heavier statement, used where a card needs an edge that reads. */
  --line-1: var(--grey-300);
  --line-2: var(--grey-400);
  --line-on-brand: rgba(255, 255, 255, 0.22);

  /* ---------------------------------------------------------------------
   * Accent roles.
   *
   * Rose is the accent. Every primary emphasis — a pressed filter, a selected
   * row, a primary button — is rose with white type, and its hover deepens
   * rather than inverting, because this system has shadow and lift to spend
   * where RawBlock only had inversion.
   * ------------------------------------------------------------------- */
  --accent-primary: var(--rose);
  --accent-primary-hover: #be123c; /* rose, one step down */
  --ink-on-accent: var(--white);

  /* ---------------------------------------------------------------------
   * --accent-tool: one hue per tool, and one job.
   *
   * Unchanged in intent from RawBlock: the value is stamped on <html> at build
   * time from tools.config.mjs and marks the thing the tool is currently
   * acting on — the word playing now, the trimmed span, the dragged point.
   * Not buttons, not headings, not borders.
   *
   * That rule matters *more* here, not less. RawBlock could afford a loose
   * accent because the rest of the page was black and white; on a page that
   * already has rose, blue and yellow in it, a tool accent that wanders onto
   * furniture becomes a fourth brand colour and the page stops parsing.
   *
   * All six clear AA on the ground they actually land on — checked against
   * this system's grounds, not RawBlock's: the four light tools sit on
   * #ffffff (7.7–8.7:1) and the two dark ones on #171320 (6.3 and 8.0:1).
   * ------------------------------------------------------------------- */
  --accent-tool: var(--ink-1);
  --ink-on-accent-tool: var(--ink-inverse);

  --accent-positive: var(--green);
  --accent-highlight: var(--yellow);
  --accent-negative: var(--red);
  --accent-link: var(--blue);

  --ink-on-green: var(--white);
  --ink-on-orange: var(--white);
  --ink-on-red: var(--white);
  --ink-on-blue: var(--white);
  /* The one that keeps yellow honest: dark ink, always. */
  --ink-on-yellow: var(--ink-1);

  /* Focus. Rose, at 3px with an offset — visible against white, against a
     colour block, and against the plate. */
  --focus-ring: var(--rose);
  --focus-ring-width: 3px;
  --focus-ring-offset: 2px;

  --overlay-scrim: rgba(23, 19, 32, 0.6);
}


/* ---- tokens/colors-dark.css ---- */
/* ---------------------------------------------------------------------------
 * The inverted set.
 *
 * This one is authored, not mechanical. CreateSpace is a light-only system —
 * its spec names a white page surface and stops — so unlike RawBlock, where
 * dark was the system's own inversion applied to the whole document, every
 * value below is a decision.
 *
 * No tool ships on it right now. Greenscreen remover and subtitle maker were
 * the two that did — video tools, where you cannot judge a key edge, spill or
 * a frame's exposure against a white page — and both moved to light so the six
 * read as one site. The set is kept complete and kept checked anyway:
 * check-design.mjs resolves every contrast pair in both modes on every build,
 * so this stays correct rather than rotting until the next tool wants it. A
 * dark mode that is only written when something needs it is a dark mode that
 * is wrong when something needs it.
 *
 * The ground is #171320 — the ramp's own darkest step, a warm violet near-black
 * rather than #000. Pure black under a rose primary reads as a different
 * system's page with this one's buttons on it; the violet keeps the two ends of
 * the ramp related.
 *
 * Three of the brand hues move, and they have to. Contrast against #171320:
 *
 *   rose   #e11d48 → #fb7185   3.3:1 is not enough for a button ink or a
 *                              heading; the lifted rose is 6.8:1 and still
 *                              unmistakably the same hue.
 *   blue   #2563eb → #93b4ff   2.9:1 → 8.9:1.
 *   green  #15803d → #4ade80   the relighting light mode needed runs the other
 *                              way here: 2.0:1 → 10.5:1.
 *   orange #b45309 → #fbbf24   2.6:1 → 10.9:1, back to the spec's own amber.
 *   red    #dc2626 → #ff6b6b   3.4:1 → 6.6:1.
 *   yellow #facc15 unchanged    11.4:1 on this ground.
 *
 * Yellow is the interesting one. On white it is fill-only and cannot be text;
 * on this ground it is one of the most legible colours in the system. The
 * fill-only rule is therefore a *light mode* rule, and --ink-on-yellow stays
 * dark in both modes because the fill itself does not move.
 *
 * Opt in per app with <html data-theme="dark">. There is deliberately no
 * prefers-color-scheme automatic mode: each tool's theme is a property of the
 * tool's job, not of the visitor's OS setting.
 * ------------------------------------------------------------------------- */

:root {
  color-scheme: light;

  /* Roles the components would otherwise hardcode. Naming them here is what
     lets the dark block below override them at all. */
  --surface-disabled: var(--grey-100);
  --ink-disabled: var(--grey-400);
  --line-disabled: var(--grey-200);
  --line-subtle: var(--grey-200);
}

[data-theme="dark"] {
  color-scheme: dark;

  /* Surfaces. The card lifts off the page rather than matching it, which is
     how depth reads without a shadow that has nothing to fall on. */
  --surface-page: var(--grey-900);
  --surface-card: #201a29;
  --surface-raised: #2a2430;
  --surface-sunken: #120f1a;
  --surface-inverse: var(--white);
  --surface-glass: rgba(42, 36, 48, 0.72);
  --surface-glass-line: rgba(255, 255, 255, 0.12);
  /* The plate on a dark page is a deeper plum block, not a white one: this
     system's plate is "a saturated field", and inverting to white would make
     the footer the brightest thing on the page. */
  --surface-brand: #2a1b2e;

  /* Ink */
  --ink-1: #f6f2f5;
  --ink-2: var(--grey-300);
  --ink-3: var(--grey-400);
  --ink-inverse: var(--grey-900);
  --ink-on-brand: #f6f2f5;
  --ink-on-brand-2: var(--grey-300);

  /* Lines */
  --line-1: #3a3244;
  --line-2: #4c4257;
  --line-on-brand: rgba(255, 255, 255, 0.18);
  --line-subtle: #2a2430;

  /* Accent */
  --accent-primary: #fb7185;
  --accent-primary-hover: #fda4af;
  --ink-on-accent: var(--grey-900);

  /* The brand and status hues, relit for #171320. See the header for ratios. */
  --rose:   #fb7185;
  --blue:   #93b4ff;
  --green:  #4ade80;
  /* Not the spec's own #fbbf24, which is what lifting #b45309 for this ground
     naturally lands on. Against --yellow (#facc15, unchanged in dark) that
     value is 1.09:1 — the warn badge and the hot badge become the same colour,
     and two status colours that cannot be told apart are worse than one. */
  --orange: #fb923c;
  --red:    #ff6b6b;

  /* Light hues need dark ink on them, so every --ink-on-* flips. */
  --ink-on-green: var(--grey-900);
  --ink-on-orange: var(--grey-900);
  --ink-on-red: var(--grey-900);
  --ink-on-blue: var(--grey-900);
  --ink-on-yellow: var(--grey-900);

  --focus-ring: #fb7185;

  /* Disabled */
  --surface-disabled: #241e2c;
  --ink-disabled: var(--grey-500);
  --line-disabled: #332c3c;

  --overlay-scrim: rgba(9, 7, 13, 0.78);
}


/* ---- tokens/typography.css ---- */
/* ---------------------------------------------------------------------------
 * The type scale.
 *
 * CreateSpace names eight steps; the site indexes into twelve, so four are
 * invented. The spec's own eight are marked; the rest are interpolated onto the
 * same ratio so nothing lands off the ladder.
 *
 * Unlike RawBlock — where the scale jumped 64 → 48 → 32 → 22 because with no
 * colour and no shadow, scale contrast was one of only two things carrying
 * hierarchy — this ramp is smooth. It does not have to shout: a rose block, a
 * yellow badge and a real shadow are all available to separate one thing from
 * another, so the type can stop doing all of it alone.
 *
 * Poppins carries the display steps at three weights and DM Sans everything
 * below. The crossover is at --text-title-1 (24px, h3), which is the last
 * Poppins step.
 * ------------------------------------------------------------------------- */

:root {
  --text-display-1: 72px; /* spec: hero, Poppins ExtraBold */
  --text-display-2: 48px; /* spec: h1, Poppins Bold */
  --text-display-3: 32px; /* spec: h2, Poppins SemiBold */
  --text-title-1: 24px;   /* spec: h3, Poppins SemiBold — last display step */
  --text-title-2: 20px;   /* invented */
  --text-title-3: 18px;   /* invented — h4, first DM Sans step */
  --text-body-lg: 18px;   /* spec */
  --text-body: 16px;      /* spec */
  --text-body-sm: 14px;   /* invented */
  --text-mono: 14px;      /* spec: Fira Code */
  --text-micro: 13px;     /* spec: caption, DM Sans Medium */
  --text-nano: 11px;      /* invented */

  /* ---------------------------------------------------------------------
   * The size a tool sets its own name at, in its own chrome.
   *
   * This role did not exist, and its absence is why the six tools drifted to
   * 80 / 20 / 16 / 15 / 15 / 13px for the same piece of text — each app picked
   * a number when it was built and nothing named the right one. A tool's name
   * is not an h1 in the document sense; it is a label on a piece of chrome,
   * and it wants one size across the site so moving between two tools does not
   * feel like moving between two products.
   *
   * 24px, which is --text-title-1 — the last Poppins step, so the name is set
   * in the display face at a display size rather than at the top of the body
   * ladder. It was 20px first, and that read as one more label in the chrome;
   * a step up is enough for the tool to announce itself on landing without
   * becoming the loudest thing on screen once there is work under it. Going
   * further means --text-display-3 at 32px, which is a headline, not a toolbar.
   * ------------------------------------------------------------------- */
  --text-app-title: var(--text-title-1);

  /* Weights. DM Sans is variable 400-700; Poppins ships the three static
     weights the spec asks for and has nothing between them. */
  --weight-regular: 400;
  --weight-medium: 500;
  --weight-semibold: 600;
  --weight-bold: 700;
  --weight-extrabold: 800;

  /* Line heights. Poppins is geometric and fairly wide, so the display steps
     are set looser than RawBlock's solid Archivo Black — set at 1 it locks up. */
  --leading-display: 1.04;
  --leading-tight: 1.12;
  --leading-snug: 1.28;
  --leading-body: 1.6;
  --leading-loose: 1.75;

  /* ---------------------------------------------------------------------
   * Tracking.
   *
   * Poppins at display sizes needs negative tracking or the geometric round
   * forms drift apart; the two uppercase roles need positive, as they always
   * do. Body is left alone.
   * ------------------------------------------------------------------- */
  --tracking-display: -0.03em;
  --tracking-title: -0.015em;
  --tracking-body: 0;
  --tracking-label: 0.08em;
  --tracking-button: 0.02em;

  /* Composite roles */
  --type-display: var(--weight-extrabold) var(--text-display-2) / var(--leading-display) var(--font-display);
  --type-title: var(--weight-semibold) var(--text-title-1) / var(--leading-tight) var(--font-display);
  --type-body: var(--weight-regular) var(--text-body) / var(--leading-body) var(--font-sans);
  --type-label: var(--weight-medium) var(--text-body-sm) / 1.2 var(--font-sans);
  --type-mono: var(--weight-regular) var(--text-mono) / var(--leading-body) var(--font-mono);
  /* Poppins semibold, set tight. The one line every tool has in common. */
  --type-app-title: var(--weight-semibold) var(--text-app-title) / 1.2 var(--font-display);
}


/* ---- tokens/spacing.css ---- */
/* ---------------------------------------------------------------------------
 * An 8px base, used regularly.
 *
 * The spec gives the base unit and nothing else, so the ladder is invented:
 * 4 / 8 / 12 / 16 / 24 / 32 / 48 / 64 / 96 / 128 / 160.
 *
 * Even, unlike RawBlock's. That system's gaps widened fast on purpose — a
 * section breathing at 120px beside a card padding at 24px was the tension it
 * asked for. CreateSpace is grid-heavy and asymmetric instead: the drama comes
 * from where blocks sit and what colour they are, not from the size of the air
 * between them, and an irregular ladder fights a grid.
 *
 * The eleven --space-* names are kept because six tools index into them.
 * ------------------------------------------------------------------------- */

:root {
  --space-0: 0px;
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;
  --space-7: 48px;
  --space-8: 64px;
  --space-9: 96px;
  --space-10: 128px;
  --space-11: 160px;

  /* Layout. The container is narrower than RawBlock's 1440 because this system
     puts cards in a grid rather than rules edge to edge, and a twelve-column
     grid at 1440 gives columns nothing on the page is shaped like. */
  --gutter: 24px;
  --page-margin: 24px;
  --page-margin-lg: 48px;
  --container-max: 1280px;
  --measure: 68ch;
  --grid-columns: 12;
  --section-gap: var(--space-9);
}


/* ---- tokens/geometry.css ---- */
/* ---------------------------------------------------------------------------
 * Radius and shadow do the job borders did.
 *
 * This is the exact inverse of RawBlock's geometry file, and the swap is the
 * substance of the port. There, weight was the only thing available to say
 * "this is a boundary, this one matters more, this one has focus", so three
 * border weights carried it and radius was 0 everywhere. Here, a card is a
 * rounded panel that sits above the page on a coloured shadow, so borders drop
 * back to a hairline and the shape carries.
 *
 * The spec names five shadows and no radii at all. The radii below are
 * inferred from "glassmorphism-inspired panels" and "bold colour blocks": a
 * block wants a generous corner, not a 4px one, so the default is 12 and the
 * panel step is 20.
 * ------------------------------------------------------------------------- */

:root {
  --radius-0: 0px;
  --radius-sm: 6px;      /* badges, chips, the checkbox */
  --radius-default: 12px; /* buttons, inputs, the ordinary card */
  --radius-lg: 20px;     /* panels, dialogs, the tool card */
  --radius-xl: 28px;     /* the hero block */
  --radius-pill: 999px;

  /* Borders. A hairline is the ordinary case; --border-thick exists for the
     places a component still needs a real edge, mostly on the glass panel
     where a shadow alone cannot separate it from what shows through. */
  --border-thin: 1px;
  --border-thick: 2px;
  --border-heavy: 3px;

  /* The old numbered names, repointed. Six tool stylesheets still say
     var(--border-1), and under this system they become a 1px grey hairline
     rather than RawBlock's 2px black rule — which is the correct reading of
     the same intent in a system where lines are quiet. */
  --border-hairline: var(--border-thin);
  --border-1: var(--border-thin);
  --border-2: var(--border-thick);

  /* ---------------------------------------------------------------------
   * Shadows, from the spec's five names.
   *
   * They are tinted, not grey. A neutral drop shadow under a rose button on a
   * white page reads as dirt; the same shadow carrying the button's own hue
   * reads as light. --shadow-color is the spec's own name for that and is the
   * one the primary button and the tool card use.
   *
   * --shadow-none is kept because the tools reference it and because
   * check-design resolves box-shadow through it.
   * ------------------------------------------------------------------- */
  --shadow-none: none;
  --shadow-sm: 0 1px 2px rgba(23, 19, 32, 0.06);
  --shadow-md: 0 4px 12px rgba(23, 19, 32, 0.08), 0 1px 3px rgba(23, 19, 32, 0.06);
  --shadow-lg: 0 12px 32px rgba(23, 19, 32, 0.12), 0 2px 8px rgba(23, 19, 32, 0.06);
  --shadow-glass: 0 8px 32px rgba(23, 19, 32, 0.10);
  --shadow-color: 0 8px 24px rgba(225, 29, 72, 0.24);
  --shadow-focus: 0 0 0 3px rgba(225, 29, 72, 0.28);

  /* The blur behind a frosted panel. Named so the one component that uses it
     and the tools that may want to match it agree on the amount. */
  --glass-blur: 14px;
}

[data-theme="dark"] {
  /* On a dark ground a shadow cast in near-black is invisible. Depth there is
     the surface stepping lighter (see colors-dark.css) with the shadow only
     deepening the edge, so these go darker and tighter rather than softer. */
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
  --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.45), 0 1px 3px rgba(0, 0, 0, 0.3);
  --shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.55), 0 2px 8px rgba(0, 0, 0, 0.35);
  --shadow-glass: 0 8px 32px rgba(0, 0, 0, 0.5);
  --shadow-color: 0 8px 24px rgba(251, 113, 133, 0.22);
  --shadow-focus: 0 0 0 3px rgba(251, 113, 133, 0.32);
}


/* ---- tokens/motion.css ---- */
/* ---------------------------------------------------------------------------
 * Motion, and this system actually has some.
 *
 * RawBlock zeroed every duration: a hover there was a full black/white
 * inversion, and easing a switch over 140ms turns it into an animation. That
 * reasoning does not survive the swap. A CreateSpace hover is a 2px lift and a
 * shadow growing under it — a physical move, and a physical move that snaps
 * reads as a glitch rather than as a state.
 *
 * Kept short anyway. The spec asks for a dynamic, portfolio-first page, not a
 * page that makes you wait: 120ms is under the threshold where a transition
 * starts feeling like latency, and nothing on an ordinary control exceeds it.
 *
 * The names and the curve tokens carry over unchanged because tool CSS
 * references them.
 * ------------------------------------------------------------------------- */

:root {
  --duration-instant: 0ms;
  --duration-fast: 120ms; /* colour, border, shadow — the ordinary state change */
  --duration-base: 180ms; /* something that travels: the switch knob, a panel */
  --duration-slow: 280ms; /* a dialog arriving */

  /* A soft ease-out on everything: the move should decelerate into place,
     which is what makes a 2px lift read as weight rather than as a jump. */
  --ease-standard: cubic-bezier(0.2, 0, 0.2, 1);
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-in: cubic-bezier(0.7, 0, 0.84, 0);

  --transition-default: all var(--duration-fast) var(--ease-standard);

  /* The lift itself, named so a card, a button and a tool card all rise by the
     same amount instead of each picking a number. */
  --lift: -2px;
}



/* ---- base.css ---- */
/* ---------------------------------------------------------------------------
 * base.css — page-level defaults.
 *
 * The layer that stops six apps looking like six apps: it sets the page
 * surface, the two typefaces, the link and selection treatment, and the focus
 * ring. Nothing a tool would have to fight.
 *
 * It carries less alone than RawBlock's did. There, an h2 in Archivo Black at
 * 48px on a white page was already the whole look before a single component
 * class applied. Here the identity is in the colour blocks and the panels, so
 * base sets a calm, well-set page and lets components.css do the talking.
 * ------------------------------------------------------------------------- */

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

html {
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--surface-page);
  color: var(--ink-1);
  font-family: var(--font-sans);
  font-size: var(--text-body);
  line-height: var(--leading-body);
  letter-spacing: var(--tracking-body);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* h1-h3 are Poppins; h4-h6 drop to DM Sans semibold. The crossover is one step
   lower than RawBlock's because Poppins at 22px still reads as a display face
   where Archivo Black at that size reads as shouting. */
h1,
h2,
h3 {
  margin: 0;
  font-family: var(--font-display);
  letter-spacing: var(--tracking-display);
  text-wrap: balance;
}

h1 {
  font-size: var(--text-display-2);
  font-weight: var(--weight-bold);
  line-height: var(--leading-display);
}

h2 {
  font-size: var(--text-display-3);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-tight);
}

h3 {
  font-size: var(--text-title-1);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-title);
}

h4,
h5,
h6 {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--text-title-3);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-snug);
  letter-spacing: var(--tracking-title);
  text-wrap: balance;
}

h5 { font-size: var(--text-body-lg); }
h6 { font-size: var(--text-body); }

p {
  margin: 0;
  text-wrap: pretty;
}

/* Blue and underlined. CreateSpace assigns blue to links *and* to secondary
   actions, so unlike RawBlock this system cannot claim the hue is reserved —
   what marks a link is the underline, and the hover thickens it rather than
   inverting the block, because inversion is not this system's language. */
a {
  color: var(--blue);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  transition: text-decoration-thickness var(--duration-fast) var(--ease-standard),
    color var(--duration-fast) var(--ease-standard);
}

a:hover {
  text-decoration-thickness: 2px;
}

/* Rose, offset, and the same ring on everything. It has to clear white, a
   colour block and the plate, which is why it is the brand hue rather than
   ink: rose is the only value legible on all three. */
/* The outline follows whatever border-radius the element already has, so
   nothing here needs to restate one — and must not, or every focused element
   would change shape on focus. */
:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring);
  outline-offset: var(--focus-ring-offset);
}

::selection {
  background: var(--yellow);
  color: var(--ink-on-yellow);
}

img,
svg,
video,
canvas {
  max-width: 100%;
  display: block;
}

button,
input,
select,
textarea {
  font: inherit;
  letter-spacing: inherit;
  color: inherit;
}

/* Native controls paint themselves in the browser's own blue otherwise. A
   slider's filled track and a checkbox's tick are the control showing what it
   is set to, which is exactly the accent's job. */
input[type="range"],
input[type="checkbox"],
input[type="radio"],
progress {
  accent-color: var(--accent-tool);
}

/* Anything the machine produced or the user typed is monospaced. */
code,
kbd,
pre,
samp {
  font-family: var(--font-mono);
  font-size: 0.9em;
}

/* A quiet hairline, not a statement. */
hr {
  border: none;
  border-top: var(--border-thin) solid var(--line-1);
  margin: var(--space-6) 0;
}

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


/* ---- components.css ---- */
/* ---------------------------------------------------------------------------
 * The component class layer.
 *
 * Same class surface as RawBlock's — .us-<component>, .us-<component>--<variant>,
 * .is-<state> — because six tools, the hub and the specimen sheet all reference
 * these names and a theme swap that renamed them would not be a theme swap.
 * What changes is every mechanic underneath.
 *
 * Two mechanics recur and are worth reading once, because both are the exact
 * opposite of the system this replaces:
 *
 *   Lift, not inversion. RawBlock's hover was a full black/white swap, which is
 *   what let it live without shadow, radius or accent colour. Here a hover
 *   raises the element 2px and grows the shadow under it, and the fill deepens
 *   one step. The state change is smaller because it does not have to be the
 *   only signal in the system.
 *
 *   Size is fixed at all states. RawBlock grew borders from 2px to 4px on focus
 *   and subtracted the difference from padding to stop the box moving. Nothing
 *   here changes border width, so nothing needs that compensation — focus is a
 *   ring drawn outside the box and press is a translate, and both leave layout
 *   alone.
 *
 * The yellow rule from colors.css is enforced structurally rather than by
 * convention: no rule below ever sets `color: var(--yellow)`. Yellow appears as
 * a fill under --ink-on-yellow, and that is all.
 * ========================================================================= */

/* ===========================================================================
   Button
   sm 32 / md 44 / lg 56. Sentence case, rounded, no border on the primary.
   =========================================================================== */

.us-btn {
  --us-btn-py: 11px;
  --us-btn-px: 22px;

  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: 44px;
  padding: var(--us-btn-py) var(--us-btn-px);
  border: var(--border-thin) solid transparent;
  border-radius: var(--radius-default);
  background: var(--accent-primary);
  color: var(--ink-on-accent);
  font: var(--weight-semibold) var(--text-body-sm) / 1.2 var(--font-sans);
  letter-spacing: var(--tracking-button);
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  box-shadow: var(--shadow-color);
  transition: background var(--duration-fast) var(--ease-standard),
    box-shadow var(--duration-fast) var(--ease-standard),
    transform var(--duration-fast) var(--ease-standard),
    color var(--duration-fast) var(--ease-standard),
    border-color var(--duration-fast) var(--ease-standard);
}

.us-btn:hover:not(:disabled):not(.is-disabled) {
  background: var(--accent-primary-hover);
  transform: translateY(var(--lift));
  box-shadow: var(--shadow-lg);
}

/* Press puts it back down. The shadow collapsing is what reads as contact. */
.us-btn:active:not(:disabled):not(.is-disabled) {
  transform: translateY(0);
  box-shadow: var(--shadow-sm);
}

.us-btn:disabled,
.us-btn.is-disabled {
  background: var(--surface-disabled);
  border-color: var(--line-disabled);
  color: var(--ink-disabled);
  box-shadow: none;
  transform: none;
  cursor: not-allowed;
}

.us-btn--sm {
  --us-btn-py: 7px;
  --us-btn-px: 14px;
  min-height: 32px;
  font-size: var(--text-micro);
  border-radius: var(--radius-sm);
}

.us-btn--lg {
  --us-btn-py: 16px;
  --us-btn-px: 32px;
  min-height: 56px;
  font-size: var(--text-body-lg);
  border-radius: var(--radius-lg);
}

.us-btn--block {
  width: 100%;
}

/* Secondary: an outlined button in the link blue, which is the second job
   CreateSpace gives that hue. No coloured shadow — only the primary gets one,
   or the page fills up with rose light. */
.us-btn--secondary {
  background: var(--surface-card);
  border-color: var(--line-2);
  color: var(--ink-1);
  box-shadow: var(--shadow-sm);
}

.us-btn--secondary:hover:not(:disabled):not(.is-disabled) {
  background: var(--surface-card);
  border-color: var(--blue);
  color: var(--blue);
  box-shadow: var(--shadow-md);
}

/* Ghost: no border, no fill. A button shaped like a link. */
.us-btn--ghost {
  border-color: transparent;
  background: transparent;
  color: var(--blue);
  box-shadow: none;
}

.us-btn--ghost:hover:not(:disabled):not(.is-disabled) {
  background: var(--surface-sunken);
  border-color: transparent;
  color: var(--blue);
  box-shadow: none;
  transform: none;
}

.us-btn--ghost:disabled,
.us-btn--ghost.is-disabled {
  background: transparent;
  border-color: transparent;
  box-shadow: none;
}

/* Destructive. Keeps a coloured shadow, in its own hue rather than the rose,
   so the warning survives the state change. */
.us-btn--danger {
  background: var(--red);
  border-color: transparent;
  color: var(--ink-on-red);
  box-shadow: 0 8px 24px rgba(220, 38, 38, 0.24);
}

.us-btn--danger:hover:not(:disabled):not(.is-disabled) {
  background: #b91c1c;
  color: var(--ink-on-red);
}

[data-theme="dark"] .us-btn--danger:hover:not(:disabled):not(.is-disabled) {
  background: #ff8f8f;
}

/* On the plate, where a rose button on a plum ground has too little to push
   against. It becomes the page surface instead and keeps the lift. */
.us-btn--on-brand {
  background: var(--surface-page);
  border-color: transparent;
  color: var(--ink-1);
  box-shadow: var(--shadow-lg);
}

.us-btn--on-brand:hover:not(:disabled):not(.is-disabled) {
  background: var(--surface-page);
  color: var(--accent-primary);
}

/* ===========================================================================
   Icon button — square-ish, 32 / 40 / 48.
   =========================================================================== */

.us-icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  padding: 0;
  border: var(--border-thin) solid var(--line-1);
  border-radius: var(--radius-default);
  background: var(--surface-card);
  color: var(--ink-2);
  text-decoration: none;
  cursor: pointer;
  transition: var(--transition-default);
}

.us-icon-btn:hover:not(:disabled) {
  border-color: var(--accent-primary);
  background: var(--surface-card);
  color: var(--accent-primary);
  box-shadow: var(--shadow-md);
}

.us-icon-btn:active:not(:disabled) {
  box-shadow: none;
}

.us-icon-btn:disabled {
  background: var(--surface-disabled);
  border-color: var(--line-disabled);
  color: var(--ink-disabled);
  cursor: not-allowed;
}

.us-icon-btn--sm { width: 32px; height: 32px; border-radius: var(--radius-sm); }
.us-icon-btn--lg { width: 48px; height: 48px; }

.us-icon-btn--solid {
  background: var(--accent-primary);
  border-color: transparent;
  color: var(--ink-on-accent);
  box-shadow: var(--shadow-color);
}

.us-icon-btn--solid:hover:not(:disabled) {
  background: var(--accent-primary-hover);
  border-color: transparent;
  color: var(--ink-on-accent);
}

.us-icon-btn--bare {
  border-color: transparent;
  background: transparent;
}

.us-icon-btn--bare:hover:not(:disabled) {
  border-color: transparent;
  background: var(--surface-sunken);
  color: var(--accent-primary);
  box-shadow: none;
}

/* ===========================================================================
   Badge — a small solid block. This is where yellow finally gets to be a fill.
   =========================================================================== */

.us-badge {
  display: inline-block;
  padding: 3px 9px;
  border: var(--border-thin) solid transparent;
  border-radius: var(--radius-sm);
  background: var(--ink-1);
  color: var(--surface-page);
  font: var(--weight-semibold) var(--text-nano) / 1.5 var(--font-sans);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  white-space: nowrap;
}

/* The status set: a soft tint of the hue with the hue's own dark value on it.
   RawBlock did the opposite — white fill, coloured border and type — because
   it had no tints. This system has a ramp and can afford them. */
.us-badge--quiet {
  background: var(--surface-sunken);
  border-color: var(--line-1);
  color: var(--ink-2);
}

.us-badge--new {
  background: var(--accent-primary);
  color: var(--ink-on-accent);
}

.us-badge--live {
  background: var(--green);
  color: var(--ink-on-green);
}

.us-badge--warn {
  background: var(--orange);
  color: var(--ink-on-orange);
}

/* The one place the spec's tertiary lands as intended: a yellow block with
   dark ink on it, 11.92:1. */
.us-badge--hot {
  background: var(--yellow);
  color: var(--ink-on-yellow);
}

/* ===========================================================================
   Tag — the filter chip. Selected fills with the accent.
   =========================================================================== */

.us-tag {
  display: inline-flex;
  align-items: center;
  padding: 7px 14px;
  border: var(--border-thin) solid var(--line-1);
  border-radius: var(--radius-pill);
  background: var(--surface-card);
  color: var(--ink-2);
  font: var(--weight-medium) var(--text-micro) / 1.3 var(--font-sans);
  letter-spacing: 0;
  text-decoration: none;
  cursor: pointer;
  transition: var(--transition-default);
}

.us-tag:hover {
  border-color: var(--accent-primary);
  color: var(--accent-primary);
}

.us-tag.is-selected,
.us-tag[aria-pressed="true"] {
  background: var(--accent-primary);
  border-color: transparent;
  color: var(--ink-on-accent);
}

/* ===========================================================================
   Fields — label, control, hint.
   =========================================================================== */

.us-field {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

/* Quieter than RawBlock's, which set labels in Archivo Black at 14px uppercase
   so the form read as a stack of blocks. Here the control has a fill and a
   radius of its own, so the label can step back and just name it. */
.us-label {
  font: var(--weight-medium) var(--text-body-sm) / 1.3 var(--font-sans);
  letter-spacing: 0;
  color: var(--ink-2);
  margin-bottom: var(--space-1);
}

/* The micro label — section eyebrows and column heads. The one uppercase
   tracked role left in the system. */
.us-micro {
  font: var(--weight-semibold) var(--text-nano) / 1.4 var(--font-sans);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--ink-3);
}

.us-hint {
  margin-top: var(--space-1);
  font: var(--weight-regular) var(--text-micro) / 1.5 var(--font-sans);
  color: var(--ink-3);
}

.us-error {
  margin-top: var(--space-1);
  font: var(--weight-medium) var(--text-micro) / 1.5 var(--font-sans);
  color: var(--red);
}

/* Inputs stay monospaced — what you typed is machine text, and that reading
   survives the swap. Fira Code rather than Space Mono. */
.us-input {
  --us-input-py: 11px;
  --us-input-px: 14px;

  display: block;
  width: 100%;
  min-height: 44px;
  padding: var(--us-input-py) var(--us-input-px);
  border: var(--border-thin) solid var(--line-2);
  border-radius: var(--radius-default);
  background: var(--surface-card);
  color: var(--ink-1);
  font: var(--weight-regular) var(--text-mono) / 1.4 var(--font-mono);
  letter-spacing: var(--tracking-body);
  transition: border-color var(--duration-fast) var(--ease-standard),
    box-shadow var(--duration-fast) var(--ease-standard);
}

.us-input::placeholder {
  color: var(--ink-3);
}

.us-input:hover:not(:disabled) {
  border-color: var(--ink-3);
}

/* Focus is the ring, drawn outside. Nothing resizes. */
.us-input:focus {
  border-color: var(--accent-primary);
  box-shadow: var(--shadow-focus);
  outline: none;
}

.us-input.is-error,
.us-input[aria-invalid="true"] {
  border-color: var(--red);
}

.us-input.is-error:focus,
.us-input[aria-invalid="true"]:focus {
  box-shadow: 0 0 0 3px rgba(220, 38, 38, 0.24);
}

.us-input:disabled {
  border-color: var(--line-disabled);
  background: var(--surface-disabled);
  color: var(--ink-disabled);
  cursor: not-allowed;
}

/* Select. The chevron is drawn as a background SVG rather than pulling in an
   icon library for two lines — currentColor cannot be used in a background
   image, so the ink is written literally and swapped for dark. */
.us-select {
  --us-input-py: 11px;
  --us-input-px: 14px;

  display: block;
  width: 100%;
  min-height: 44px;
  padding: var(--us-input-py) 40px var(--us-input-py) var(--us-input-px);
  border: var(--border-thin) solid var(--line-2);
  border-radius: var(--radius-default);
  background-color: var(--surface-card);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpolyline points='1.5,4.5 6,9 10.5,4.5' fill='none' stroke='%235f5660' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 14px center;
  color: var(--ink-1);
  font: var(--weight-regular) var(--text-mono) / 1.4 var(--font-mono);
  cursor: pointer;
  appearance: none;
  -webkit-appearance: none;
  transition: border-color var(--duration-fast) var(--ease-standard),
    box-shadow var(--duration-fast) var(--ease-standard);
}

[data-theme="dark"] .us-select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpolyline points='1.5,4.5 6,9 10.5,4.5' fill='none' stroke='%23aba1a8' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}

.us-select:hover:not(:disabled) {
  border-color: var(--ink-3);
}

.us-select:focus {
  border-color: var(--accent-primary);
  box-shadow: var(--shadow-focus);
  outline: none;
}

.us-select:disabled {
  border-color: var(--line-disabled);
  background-color: var(--surface-disabled);
  color: var(--ink-disabled);
  cursor: not-allowed;
}

/* Checkbox — 20px, rose when checked, white tick. */
.us-checkbox {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  font: var(--weight-regular) var(--text-body) / 1.4 var(--font-sans);
  color: var(--ink-1);
  cursor: pointer;
}

.us-checkbox input[type="checkbox"] {
  appearance: none;
  -webkit-appearance: none;
  flex: none;
  width: 20px;
  height: 20px;
  margin: 0;
  border: var(--border-thin) solid var(--line-2);
  border-radius: var(--radius-sm);
  background: var(--surface-card);
  cursor: pointer;
  transition: var(--transition-default);
}

.us-checkbox input[type="checkbox"]:checked {
  border-color: transparent;
  background-color: var(--accent-primary);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 14 14'%3E%3Cpolyline points='2.5,7.2 5.6,10.3 11.5,4' fill='none' stroke='%23ffffff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: center;
}

[data-theme="dark"] .us-checkbox input[type="checkbox"]:checked {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 14 14'%3E%3Cpolyline points='2.5,7.2 5.6,10.3 11.5,4' fill='none' stroke='%23171320' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}

.us-checkbox input[type="checkbox"]:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring);
  outline-offset: var(--focus-ring-offset);
}

.us-checkbox input[type="checkbox"]:disabled {
  border-color: var(--line-disabled);
  background-color: var(--surface-disabled);
  cursor: not-allowed;
}

.us-checkbox:has(input:disabled) {
  color: var(--ink-disabled);
  cursor: not-allowed;
}

/* Radio. Round here is unremarkable — in RawBlock it was the system's single
   granted exception. */
.us-radio {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  font: var(--weight-regular) var(--text-body) / 1.4 var(--font-sans);
  color: var(--ink-1);
  cursor: pointer;
}

.us-radio input[type="radio"] {
  appearance: none;
  -webkit-appearance: none;
  display: grid;
  place-content: center;
  flex: none;
  width: 20px;
  height: 20px;
  margin: 0;
  border: var(--border-thin) solid var(--line-2);
  border-radius: var(--radius-pill);
  background: var(--surface-card);
  cursor: pointer;
  transition: var(--transition-default);
}

.us-radio input[type="radio"]:checked {
  border-color: var(--accent-primary);
}

.us-radio input[type="radio"]::before {
  content: "";
  width: 10px;
  height: 10px;
  border-radius: var(--radius-pill);
  background: var(--accent-primary);
  transform: scale(0);
  transition: transform var(--duration-base) var(--ease-out);
}

.us-radio input[type="radio"]:checked::before {
  transform: scale(1);
}

.us-radio input[type="radio"]:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring);
  outline-offset: var(--focus-ring-offset);
}

.us-radio input[type="radio"]:disabled {
  border-color: var(--line-disabled);
  background: var(--surface-disabled);
  cursor: not-allowed;
}

/* Switch — a pill with a round knob. The knob travels, which is the one place
   --duration-base earns its 180ms. */
.us-switch {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  font: var(--weight-regular) var(--text-body) / 1.4 var(--font-sans);
  color: var(--ink-1);
  cursor: pointer;
}

.us-switch input[type="checkbox"] {
  appearance: none;
  -webkit-appearance: none;
  position: relative;
  flex: none;
  width: 44px;
  height: 26px;
  margin: 0;
  border: var(--border-thin) solid var(--line-2);
  border-radius: var(--radius-pill);
  background: var(--surface-sunken);
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-standard),
    border-color var(--duration-fast) var(--ease-standard);
}

.us-switch input[type="checkbox"]::after {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 20px;
  height: 20px;
  border-radius: var(--radius-pill);
  background: var(--surface-card);
  box-shadow: var(--shadow-sm);
  transition: left var(--duration-base) var(--ease-out);
}

.us-switch input[type="checkbox"]:checked {
  border-color: transparent;
  background: var(--accent-primary);
}

.us-switch input[type="checkbox"]:checked::after {
  left: 20px;
}

.us-switch input[type="checkbox"]:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring);
  outline-offset: var(--focus-ring-offset);
}

.us-switch input[type="checkbox"]:disabled {
  border-color: var(--line-disabled);
  background: var(--surface-disabled);
  cursor: not-allowed;
}

.us-switch input[type="checkbox"]:disabled::after {
  background: var(--line-disabled);
  box-shadow: none;
}

/* File drop — every tool here starts by taking a file, so this is a primitive
   rather than a one-off. */
.us-filedrop {
  padding: var(--space-7) var(--space-5);
  border: var(--border-thick) dashed var(--line-2);
  border-radius: var(--radius-lg);
  background: var(--surface-sunken);
  text-align: center;
  cursor: pointer;
  transition: var(--transition-default);
}

.us-filedrop:hover,
.us-filedrop.is-over {
  border-color: var(--accent-primary);
  background: var(--surface-card);
  box-shadow: var(--shadow-md);
}

.us-filedrop-label {
  font: var(--weight-semibold) var(--text-title-2) / 1.3 var(--font-display);
  letter-spacing: var(--tracking-title);
  color: var(--ink-1);
}

.us-filedrop-hint {
  margin-top: var(--space-2);
  font: var(--weight-regular) var(--text-micro) / 1.5 var(--font-mono);
  color: var(--ink-3);
}

/* ===========================================================================
   Card — a rounded panel that sits above the page.

   The four variants are the four kinds of surface this system has: ordinary,
   lifted, sunken, and the inverted block. RawBlock expressed all four with
   border weight because it had nothing else; here they are genuinely different
   surfaces.
   =========================================================================== */

.us-card {
  padding: var(--space-5);
  border: var(--border-thin) solid var(--line-1);
  border-radius: var(--radius-lg);
  background: var(--surface-card);
  color: var(--ink-1);
  box-shadow: var(--shadow-sm);
}

.us-card--elevated {
  border-color: transparent;
  box-shadow: var(--shadow-lg);
}

.us-card--raised {
  background: var(--surface-sunken);
  box-shadow: none;
}

.us-card--brand,
.us-card--inverse {
  background: var(--surface-inverse);
  border-color: transparent;
  color: var(--ink-inverse);
}

/* The frosted panel the spec asks for, and the one component that uses the
   blur. It only means anything over a colour block — on a plain white section
   it is an expensive way to draw a white card. */
.us-card--glass {
  background: var(--surface-glass);
  border-color: var(--surface-glass-line);
  box-shadow: var(--shadow-glass);
  backdrop-filter: blur(var(--glass-blur));
  -webkit-backdrop-filter: blur(var(--glass-blur));
}

.us-card.is-interactive {
  cursor: pointer;
  transition: transform var(--duration-fast) var(--ease-standard),
    box-shadow var(--duration-fast) var(--ease-standard);
}

.us-card.is-interactive:hover {
  transform: translateY(var(--lift));
  box-shadow: var(--shadow-lg);
}

/* ===========================================================================
   Tool card — the hub's main unit, and the one component the landing page is
   made of. A screenshot on a saturated plate with the name under it.
   =========================================================================== */

.us-toolcard {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border: var(--border-thin) solid var(--line-1);
  border-radius: var(--radius-lg);
  background: var(--surface-card);
  color: var(--ink-1);
  text-decoration: none;
  cursor: pointer;
  box-shadow: var(--shadow-sm);
  transition: transform var(--duration-fast) var(--ease-standard),
    box-shadow var(--duration-fast) var(--ease-standard),
    border-color var(--duration-fast) var(--ease-standard);
}

.us-toolcard:hover {
  transform: translateY(var(--lift));
  border-color: transparent;
  box-shadow: var(--shadow-lg);
}

.us-toolcard.is-disabled {
  border-color: var(--line-disabled);
  color: var(--ink-disabled);
  box-shadow: none;
  transform: none;
  cursor: default;
}

/* The plate holds a shape, not a height.
 *
 * Every tool's shot is a 1200×630 landscape image. A pixel height crops it by
 * whatever the card's width happens to be, which changes with the breakpoint;
 * a ratio scales the plate with the card, so the same amount of picture is
 * visible at every size. 16:9 rather than the shot's own 1.9:1, because the
 * slightly taller box is what keeps a narrow card's plate from reading as a
 * strip. */
.us-toolcard-plate {
  position: relative;
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background: var(--surface-sunken);
}

.us-toolcard-plate img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top center;
  transition: transform var(--duration-slow) var(--ease-out);
}

/* The shot pushes in slightly. It is the only scale move in the system and it
   is on the one element that is a picture rather than a control. */
.us-toolcard:hover .us-toolcard-plate img {
  transform: scale(1.03);
}

.us-toolcard-body {
  display: flex;
  flex: 1 0 auto;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-4) var(--space-5) var(--space-5);
}

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

.us-toolcard-name {
  font: var(--weight-semibold) var(--text-title-2) / 1.2 var(--font-display);
  letter-spacing: var(--tracking-title);
  color: inherit;
}

.us-toolcard-desc {
  margin: 0;
  font: var(--weight-regular) var(--text-body-sm) / 1.55 var(--font-sans);
  color: var(--ink-2);
}

/* ===========================================================================
   List — text rows separated by hairlines.
   =========================================================================== */

.us-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.us-list > li {
  padding: var(--space-3) var(--space-3);
  border-radius: var(--radius-sm);
  font: var(--weight-regular) var(--text-body) / var(--leading-body) var(--font-sans);
  transition: background var(--duration-fast) var(--ease-standard);
}

.us-list > li + li {
  border-top: var(--border-thin) solid var(--line-1);
  border-top-left-radius: 0;
  border-top-right-radius: 0;
}

.us-list > li a {
  color: inherit;
  text-decoration: none;
}

.us-list > li:hover {
  background: var(--surface-sunken);
}

.us-list > li.is-active {
  background: var(--accent-primary);
  color: var(--ink-on-accent);
  border-top-color: transparent;
}

/* ===========================================================================
   Tabs — the active tab is a rose rule under it.
   =========================================================================== */

.us-tabs {
  display: flex;
  gap: var(--space-6);
  border-bottom: var(--border-thin) solid var(--line-1);
}

.us-tab {
  padding: 0 0 var(--space-3);
  margin-bottom: calc(var(--border-thin) * -1);
  border: none;
  border-bottom: var(--border-thick) solid transparent;
  border-radius: 0;
  background: transparent;
  color: var(--ink-3);
  font: var(--weight-medium) var(--text-body-sm) / 1.2 var(--font-sans);
  letter-spacing: 0;
  cursor: pointer;
  transition: var(--transition-default);
}

.us-tab:hover {
  color: var(--ink-1);
}

.us-tab.is-active,
.us-tab[aria-selected="true"] {
  color: var(--accent-primary);
  border-bottom-color: var(--accent-primary);
}

/* ===========================================================================
   Dialog — a lifted panel on a scrim, and the one place the scrim blurs.
   =========================================================================== */

.us-dialog-scrim {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-5);
  background: var(--overlay-scrim);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
}

.us-dialog {
  width: 100%;
  max-width: 520px;
  border: var(--border-thin) solid var(--line-1);
  border-radius: var(--radius-lg);
  background: var(--surface-card);
  color: var(--ink-1);
  box-shadow: var(--shadow-lg);
}

.us-dialog-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-5);
  padding: var(--space-5) var(--space-5) 0;
}

.us-dialog-title {
  margin: 0;
  font: var(--weight-semibold) var(--text-title-1) / var(--leading-tight) var(--font-display);
  letter-spacing: var(--tracking-title);
  color: var(--ink-1);
}

.us-dialog-body {
  padding: var(--space-4) var(--space-5) var(--space-5);
  font: var(--weight-regular) var(--text-body) / var(--leading-body) var(--font-sans);
  color: var(--ink-2);
}

.us-dialog-foot {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  border-top: var(--border-thin) solid var(--line-1);
}

/* ===========================================================================
   Tooltip — a small dark block set in Fira Code. It is a machine annotation,
   so it stays machine type.
   =========================================================================== */

.us-tooltip {
  position: relative;
  display: inline-flex;
}

.us-tooltip-label {
  position: absolute;
  bottom: calc(100% + 6px);
  left: 50%;
  z-index: 50;
  transform: translateX(-50%);
  max-width: 260px;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  background: var(--surface-inverse);
  color: var(--ink-inverse);
  font: var(--weight-regular) var(--text-micro) / 1.4 var(--font-mono);
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  box-shadow: var(--shadow-md);
  transition: opacity var(--duration-fast) var(--ease-standard);
}

.us-tooltip:hover .us-tooltip-label,
.us-tooltip:focus-within .us-tooltip-label {
  opacity: 1;
}

/* ===========================================================================
   Layout helpers
   =========================================================================== */

.us-container {
  width: 100%;
  max-width: 1200px;
  margin: 0 auto;
  padding-inline: var(--page-margin);
}

.us-section {
  padding-block: var(--space-9);
}

/* The plate is the saturated section: a deep plum block that a hero, a header
   or a footer fills edge to edge. It never floats. */
.us-plate {
  background: var(--surface-brand);
  color: var(--ink-on-brand);
}

/* Links on the plate cannot be the link blue: #2563eb on #171320 is 2.9:1.
   They go to the plate's own ink and keep the underline that says "link".
   Buttons are excluded by class rather than by specificity games: .us-plate a
   is (0,1,1) and would otherwise beat .us-btn--on-brand's own colour at
   (0,1,0), painting the plate's ink on a light button. */
.us-plate a:not([class*="us-btn"]) {
  color: var(--ink-on-brand);
  text-decoration: underline;
  text-underline-offset: 3px;
}

.us-plate a:not([class*="us-btn"]):hover {
  color: var(--yellow);
}

.us-plate hr {
  border-top-color: var(--line-on-brand);
}

/* Prices, times, counts and dimensions are always tabular. */
.us-num {
  font-variant-numeric: tabular-nums;
}

/* Read aloud, never drawn. Clipped rather than display:none, which would take
   it out of the accessibility tree too. */
.us-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Body copy is capped at 68ch. */
.us-measure {
  max-width: var(--measure);
}

@media (max-width: 767px) {
  .us-section {
    padding-block: var(--space-7);
  }
  .us-container {
    padding-inline: var(--space-4);
  }
}

@media (min-width: 1024px) {
  .us-container {
    padding-inline: var(--page-margin-lg);
  }
}

