﻿  :root {
    --color-dark: #1a1a2e;

    /* ---- Theme: gold palette --------------------------------------
       Every gold/text/wood colour on the page reads from here. Swap
       a theme by changing these values only — nothing else should
       ever hardcode a hex again. JS reads the same variables via
       THEME in 03-ornaments.js (getComputedStyle on :root), so the
       ornament gradients and cloth stay in sync automatically. */
    --gold-dark: #8a6f33;    /* gradient shadow stop */
    --gold-1: #c2a458;       /* stroke outlines, medallion watermark */
    --gold-2: #c9a84c;       /* the "main" gold — links, borders, accents */
    --gold-2-rgb: 201, 168, 76; /* same colour, components only — for rgba() borders/glows */
    --gold-3: #9c7f3c;       /* gradient stop */
    --gold-4: #d9be7c;       /* gradient stop */
    --gold-5: #e6cd8a;       /* small diamond accents, flourish tips */
    --gold-6: #b8912c;       /* nav underline gradient */
    --gold-fill: #A88D46;    /* flat fallback fill (no gradient) */
    --gold-light: #f4e2ab;   /* gradient highlight stop */
    --gold-pale: #ffeec0;    /* nav hover text / underline gradient */
    --gold-pale-2: #e9c16e;  /* spark halo mid stop */
    --gold-cream: #f0d08a;   /* spark core outer stop */

    --sheen: #fffdf2;        /* sheen sweep band */
    --sheen-bright: #fffdf5; /* spark core centre */

    --wood-1: #3b2314;
    --wood-2: #6b4226;
    --wood-3: #a9754a;
    --wood-4: #4a2c17;
    --wood-5: #7c5232;
    --wood-stroke: #5c3a20;

    --ink-brightest: #fdf6e6; /* titles */
    --ink-bright: #e4dac0;    /* body values, links */
    --ink-mid: #d8cdb4;       /* body text */
    --ink-mid-2: #cdc0a4;     /* kicker text */
    --ink-soft: #a99e88;      /* soft body / inactive pills */
    --ink-dim: #a08a52;       /* eyebrow labels */
    --ink-dim-2: #8e7d55;     /* dt labels */
    --ink-faint: #7d6f4e;     /* footnotes */

    --accent-heart: #c0554a;
    --gold-nav: #b99f63;      /* nav link resting colour */
    --gold-hover: #ffe9b0;    /* contact-row link hover */
  }

  /* ---- Alternate themes -------------------------------------------
     Pick one by setting data-theme on <html> (see index.html), e.g.
     <html data-theme="emerald">. Omitting the attribute keeps the
     default gold theme above. Each block only overrides the palette
     variables — every rule in this file already reads them, so no
     other CSS has to change. */

  /* Silver: cool steel/pewter instead of gold, same dark ground. */
  html[data-theme="silver"] {
    --gold-dark: #5b6470;
    --gold-1: #9aa7b5;
    --gold-2: #c3cdd8;
    --gold-2-rgb: 195, 205, 216;
    --gold-3: #7c8894;
    --gold-4: #e6ecf2;
    --gold-5: #dbe3ea;
    --gold-6: #8fa0ac;
    --gold-fill: #8b97a3;
    --gold-light: #eef2f6;
    --gold-pale: #f2f6fa;
    --gold-pale-2: #b8c4cf;
    --gold-cream: #d7e0e8;
    --sheen: #fbfdff;
    --sheen-bright: #ffffff;
    --wood-1: #262b31;
    --wood-2: #3d454d;
    --wood-3: #5b6670;
    --wood-4: #1c2024;
    --wood-5: #4a535c;
    --wood-stroke: #2c3239;
    --ink-brightest: #f3f6f9;
    --ink-bright: #dbe2e8;
    --ink-mid: #c4cdd5;
    --ink-mid-2: #b6c0c9;
    --ink-soft: #9aa5ad;
    --ink-dim: #8b97a1;
    --ink-dim-2: #7a848d;
    --ink-faint: #626b72;
    --accent-heart: #b56a6a;
    --gold-nav: #a5b1bb;
    --gold-hover: #eef2f6;
  }

  /* Emerald: green/gold, same warmth register as the default but a
     green primary instead of gold for the main accent. */
  html[data-theme="emerald"] {
    --gold-dark: #2f5b40;
    --gold-1: #4f8f68;
    --gold-2: #5fae7c;
    --gold-2-rgb: 95, 174, 124;
    --gold-3: #3d7350;
    --gold-4: #a9dfbc;
    --gold-5: #bdeecb;
    --gold-6: #3f8f5c;
    --gold-fill: #3f7d55;
    --gold-light: #d3f2dd;
    --gold-pale: #e3fbea;
    --gold-pale-2: #7cc797;
    --gold-cream: #cdeed9;
    --sheen: #f2fff6;
    --sheen-bright: #ffffff;
    --wood-1: #241b12;
    --wood-2: #4a3320;
    --wood-3: #7a5a35;
    --wood-4: #2e2113;
    --wood-5: #5c4226;
    --wood-stroke: #3a2a18;
    --ink-brightest: #f2fbf4;
    --ink-bright: #d3e8d9;
    --ink-mid: #c0dbc8;
    --ink-mid-2: #b3d6bd;
    --ink-soft: #94b89f;
    --ink-dim: #7fa88b;
    --ink-dim-2: #6f9679;
    --ink-faint: #5a7a63;
    --accent-heart: #c0554a;
    --gold-nav: #8fbf9d;
    --gold-hover: #cdf2d7;
  }

  /* Ruby: deep red/rose primary, for a bolder jewel-tone look. */
  html[data-theme="ruby"] {
    --gold-dark: #5c1f26;
    --gold-1: #a84656;
    --gold-2: #c85a68;
    --gold-2-rgb: 200, 90, 104;
    --gold-3: #8a3947;
    --gold-4: #e8a2ac;
    --gold-5: #f0bcc3;
    --gold-6: #a83f4f;
    --gold-fill: #8f4350;
    --gold-light: #f3d3d8;
    --gold-pale: #ffe2e6;
    --gold-pale-2: #d98c97;
    --gold-cream: #f0c3c9;
    --sheen: #fff3f4;
    --sheen-bright: #ffffff;
    --wood-1: #2a1214;
    --wood-2: #4a2226;
    --wood-3: #6e3a3f;
    --wood-4: #1f0e10;
    --wood-5: #5a2c30;
    --wood-stroke: #3a181b;
    --ink-brightest: #fbeef0;
    --ink-bright: #e8d0d3;
    --ink-mid: #d9bcc0;
    --ink-mid-2: #d1aeb3;
    --ink-soft: #b28a8f;
    --ink-dim: #a1747a;
    --ink-dim-2: #8c6469;
    --ink-faint: #6e4f53;
    --accent-heart: #e6c15a;
    --gold-nav: #cf8890;
    --gold-hover: #ffd8dd;
  }

  /* Turquoise: blue/teal primary, a cooler jewel tone. */
  html[data-theme="turquoise"] {
    --gold-dark: #144a52;
    --gold-1: #2f8b96;
    --gold-2: #3fb3bf;
    --gold-2-rgb: 63, 179, 191;
    --gold-3: #266f79;
    --gold-4: #9fe3ea;
    --gold-5: #bdf0f4;
    --gold-6: #2b98a3;
    --gold-fill: #2c7d87;
    --gold-light: #d6f5f8;
    --gold-pale: #e2fbfc;
    --gold-pale-2: #6fcdd6;
    --gold-cream: #c9eef2;
    --sheen: #f0feff;
    --sheen-bright: #ffffff;
    --wood-1: #12242a;
    --wood-2: #1f4249;
    --wood-3: #366870;
    --wood-4: #0c1a1e;
    --wood-5: #2c545c;
    --wood-stroke: #172e33;
    --ink-brightest: #ecfbfc;
    --ink-bright: #c9e6e8;
    --ink-mid: #b3d9dc;
    --ink-mid-2: #a4d1d5;
    --ink-soft: #85b3b7;
    --ink-dim: #6fa0a5;
    --ink-dim-2: #5f8c91;
    --ink-faint: #4a6f73;
    --accent-heart: #d9836a;
    --gold-nav: #7ec4cb;
    --gold-hover: #d4f7fa;
  }

  * { margin: 0; padding: 0; box-sizing: border-box; }

  body {
    background: var(--color-dark);
    overflow: hidden;
    height: 100vh;
  }

  #bg-canvas {
    position: fixed;
    inset: 0;
    z-index: 0;
    pointer-events: none;
  }

  #corner-top-left {
    position: fixed;
    top: 2rem;
    left: 2rem;
    width: clamp(150px, 18vw, 285px);
    z-index: 1;
    pointer-events: auto;
    perspective: 650px;
  }

  #corner-top-left svg {
    display: block;
    width: 100%;
    height: auto;
  }

  /* Shared by every ornament: the outline drawn first, then the gold fill
     (whose gradient is applied inline, per ornament). */
  .stroke-pass {
    fill: none !important;
    stroke: var(--gold-1);
    stroke-width: 1;
  }

  .fill-pass {
    fill: var(--gold-fill);
    stroke: none;
  }

  /* Faint medallion watermark, large and glowing behind the left side of
     the composition — pure decoration (no draw-on, no tilt), so it sits
     under everything else and never intercepts the pointer. */
  #medallion-watermark {
    position: fixed;
    top: 50%;
    left: -12vw;
    width: clamp(480px, 62vw, 1000px);
    /* -15% of its own height is the resting offset; state 3 slides it with
       GSAP's x/y on top of this, so the base stays in translate() and the
       animated part rides in the same transform without fighting it. */
    translate: 0 -15%;
    z-index: 0;
    opacity: 0.03;
    pointer-events: none;
    filter: drop-shadow(0 0 45px rgba(var(--gold-2-rgb), 0.4));
  }

  #medallion-watermark svg {
    display: block;
    width: 100%;
    height: auto;
  }

  /* Empty frame waiting in state 3 (one screen below the intro), meant to
     later hold a carpet image. Lives inside #page like everything else
     anchored to the pan, so panTo(-innerHeight) alone brings it into view —
     "top" is one viewport height down plus how far it should sit from that
     new screen's own top edge.

     #frame-wrap carries the fixed position/size math (unchanged from when
     #corner-frame did); #corner-frame is now an absolute-fill child holding
     only the gold border SVG, because loadOrnament() replaces its host's
     innerHTML wholesale — the carpet <img> and zoom layer have to live
     outside that host or the fetch overwrites them. */
  #frame-wrap {
    position: fixed;
    /* Portrait art (1046x1500) — sized off the viewport's short side so it
       reads as a large centrepiece rather than a corner ornament, capped by
       width so it never overruns a narrow screen. */
    --frame-w: min(34vw, 62vh);
    /* Centred in the second screen's own height: 100vh clears the fold,
       +50vh reaches that screen's vertical middle, then back up by half
       the art's rendered height (width * 1500/1046, its intrinsic ratio)
       so the frame's middle — not its top — lands there. Both hint threads
       then travel an equal drop from the frame's top/bottom anchors down
       to their turns. */
    top: calc(100vh + 50vh - (var(--frame-w) * 1500 / 1046) / 2);
    left: 3vw;
    width: var(--frame-w);
    height: calc(var(--frame-w) * 1500 / 1046);
    /* Above #link-layer (z-index 1): the hint line's two legs both end at
       this frame's top/bottom anchors, so the last stretch of each should
       duck behind the carpet's edge rather than draw across the rug. */
    z-index: 2;
    opacity: 0;
    /* auto, not none: this box now also carries the carpet + hover-zoom
       listeners, which need real pointer events over it. #corner-frame's
       own SVG border stays pointer-events:none (see below) so it never
       blocks them. */
    pointer-events: auto;
    transform-origin: 50% 0%;
  }

  /* Top-to-bottom reveal, the same --reveal vocabulary as the flower mask
     but a straight vertical edge instead of a diagonal one — the frame
     uncovers/hides like a curtain dropping, matching the line arriving
     from the top. --reveal 0 = fully hidden, 1 = fully shown. Only used on
     a repeat entry into state 3 (spawnFrame's own spin plays on the first);
     kept as a separate mask, not opacity, so re-entries never replay the
     tilt-in. */
  #frame-wrap.frame-reveal {
    --reveal: 0;
    -webkit-mask-image: linear-gradient(to bottom, #000 calc(var(--reveal) * 100%), transparent calc(var(--reveal) * 100%));
    mask-image: linear-gradient(to bottom, #000 calc(var(--reveal) * 100%), transparent calc(var(--reveal) * 100%));
  }

  #corner-frame {
    position: absolute;
    inset: 0;
    /* The border SVG's own painted shapes would otherwise hit-test and
       intercept pointermove before it reaches the carpet beneath — this
       decoration was never meant to be interactive (see loadOrnament's
       `static` option, which already skips its tilt/sheen rig). */
    pointer-events: none;
  }

  #corner-frame svg {
    display: block;
    width: 100%;
    height: auto;
    filter: drop-shadow(6px 10px 10px rgba(0, 0, 0, 0.55));
  }

  /* The carpet itself: sits behind the frame's gold border art (z-index
     below #corner-frame), inset just enough that the border reads as a
     frame around it rather than over it. Hover/zoom listen on #frame-wrap
     so they cover the same box the border does.

     #frame-carpet-box is a positioned box sized to that inset rectangle —
     createCloth() sizes its canvas off output.parentElement's own 100%, so
     the canvas needs a parent already cropped to the carpet's box rather
     than the full (uninset) #frame-wrap, or its bleed maths come out
     against the wrong rectangle. */
  #frame-carpet-box {
    position: absolute;
    inset: 11% 13%;
    width: 74%;
    height: 78%;
    transition: transform 0.5s ease;
    transform-origin: 50% 30%;
    /* Native magnifying-glass pointer — reads as "this zooms" without a
       custom cursor image. */
    cursor: zoom-in;
  }

  #frame-wrap:hover #frame-carpet-box {
    transform: scale(1.035);
  }

  #frame-carpet {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    border-radius: 2px;
    opacity: 0;
    /* clip-path unrolls the rug top-to-bottom instead of a flat fade —
       the near edge appears first, like a carpet being thrown open. */
    clip-path: inset(0 0 100% 0);
    filter: drop-shadow(4px 8px 14px rgba(0, 0, 0, 0.5));
  }

  /* The cloth canvas cross-fades over the plain <img> once the WebGL
     ripple mounts, same handoff as #logo-mark .cloth-out. */
  #frame-carpet-box .cloth-out {
    position: absolute;
    pointer-events: none;
    filter: drop-shadow(4px 8px 14px rgba(0, 0, 0, 0.5));
  }

  /* The carpet's story, beside the frame in state 3. Positioned the same
     way #frame-wrap is — fixed, one screen down — so it pans with the page
     and lands in the second screen's vertical middle next to the art.
     Hidden by default; 07-states.js fades/slides it in on entry to state 3
     and back out on the way up, alongside the frame and thread. */
  #carpet-info {
    position: fixed;
    --frame-w: min(34vw, 62vh);
    top: calc(100vh + 50vh - (var(--frame-w) * 1500 / 1046) / 2);
    /* Pinned to the right edge and sized by width, NOT by a left/right pair
       plus max-width — that combination resolved the box narrower than its
       slot and then let the auto margin drift it inward, which is what left
       the arabic hanging in mid-air instead of starting hard at the right.
       The measure is capped so the lines stay readable; whatever is left
       over falls on the left, between here and the frame. */
    right: 6vw;
    width: min(34rem, calc(94vw - 3vw - var(--frame-w) - 5vw));
    height: calc(var(--frame-w) * 1500 / 1046);
    display: flex;
    flex-direction: column;
    /* align-items:stretch would be fine for the text, but the swatch row is
       a flex child that would otherwise size to its content and float; the
       children each pin themselves right instead. */
    justify-content: center;
    z-index: 3;
    text-align: right;
    /* The container itself stays put and fully opaque — each child fades in
       on its own (see showInfo's stagger in 07-states.js), so the block
       assembles line by line instead of arriving as one slab. Only the
       swatches take pointer events; the rest is text. */
    pointer-events: none;
  }

  /* Every direct child is a stagger step. Opacity/transform here are the
     resting "not yet revealed" state GSAP animates from and back to. */
  #carpet-info > * {
    opacity: 0;
    transform: translateX(-20px);
  }

  .ci-swatches, .ci-designs { pointer-events: auto; }

  .ci-eyebrow {
    font-family: 'Amiri', 'Georgia', serif;
    font-size: clamp(0.85rem, 1.15vw, 1.05rem);
    letter-spacing: 0.3em;
    color: var(--ink-dim);
    margin-bottom: 0.5em;
  }

  .ci-title {
    font-family: 'Amiri', 'Georgia', serif;
    font-weight: 700;
    font-size: clamp(1.8rem, 3.2vw, 3rem);
    line-height: 1.35;
    color: var(--ink-brightest);
    text-shadow: 3px 5px 6px rgba(0, 0, 0, 0.55);
  }

  /* Same diamond-and-taper motif as the wordmark's flourishes, but pinned
     to the right so it reads as an underline for the RTL title rather than
     a centred separator. */
  .ci-rule {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    flex-direction: row-reverse;
    gap: 0.5rem;
    margin: 0.75em 0 0.9em;
  }

  .ci-diamond {
    width: 5px;
    height: 5px;
    background: var(--gold-5);
    transform: rotate(45deg);
    flex: none;
  }

  .ci-body {
    font-family: 'Amiri', 'Georgia', serif;
    font-size: clamp(1.05rem, 1.35vw, 1.28rem);
    line-height: 1.75;
    color: var(--ink-mid);
    margin: 0 0 0.8em;
    /* Reserved for the longest design copy so switching designs never
       reflows the swatches/note below — shorter text just leaves gap. */
    min-height: calc(1.75em * 4);
  }

  .ci-body-soft { color: var(--ink-soft); min-height: calc(1.75em * 2); }

  /* Two columns of label/value; the gold hairline above each row is what
     keeps it reading as a spec sheet rather than more prose. */
  .ci-specs {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.7rem 2rem;
    margin: 0.4em 0 0;
  }

  .ci-spec {
    border-top: 1px solid rgba(var(--gold-2-rgb), 0.28);
    padding-top: 0.55rem;
  }

  .ci-spec dt {
    font-family: 'Amiri', 'Georgia', serif;
    font-size: 0.86rem;
    letter-spacing: 0.18em;
    color: var(--ink-dim-2);
    margin-bottom: 0.3em;
  }

  .ci-spec dd {
    font-family: 'Amiri', 'Georgia', serif;
    font-size: clamp(0.95rem, 1.15vw, 1.12rem);
    color: var(--ink-bright);
    margin: 0;
  }

  /* ---- Colour swatches ------------------------------------------------
     The bare medallion motifs, no disc or frame behind them — the art is
     the button. Selection reads through the art itself (a dim/desaturated
     resting state, full colour and a lift when chosen) rather than a ring.

     Sized by height, not width: the three files have different aspect
     ratios and margins (1199x1312, 1024x1536, 1095x1437) but near-identical
     ink coverage (94-96% of canvas height), so a fixed height lands all
     three at the same visual size while width follows the art.

     BLUE's source ships with a dark glow baked in rather than a cut-out
     background; mix-blend-mode: screen drops that near-black to nothing
     against the page, so all three read as one set despite the mismatch. */
  /* Design picker: text pills, not thumbnails — the colour row below is
     already the visual choice, so a second image grid would compete with
     it. row-reverse packs them from the right like everything else here. */
  .ci-designs-wrap { margin-top: 1.4em; }

  .ci-designs {
    display: flex;
    flex-direction: row-reverse;
    justify-content: flex-start;
    gap: 0.6rem;
  }

  .ci-design {
    font-family: 'Amiri', 'Georgia', serif;
    font-size: clamp(0.9rem, 1.1vw, 1.05rem);
    color: var(--ink-soft);
    background: none;
    border: 1px solid rgba(var(--gold-2-rgb), 0.28);
    border-radius: 2px;
    padding: 0.4em 1.1em;
    cursor: pointer;
    transition: color 260ms ease, border-color 260ms ease, background 260ms ease;
  }

  .ci-design:hover { color: var(--ink-bright); border-color: rgba(var(--gold-2-rgb), 0.55); }

  .ci-design.is-active {
    color: var(--ink-brightest);
    border-color: var(--gold-2);
    background: rgba(var(--gold-2-rgb), 0.12);
  }

  .ci-colors { margin-top: 1.4em; }

  .ci-colors-label {
    font-family: 'Amiri', 'Georgia', serif;
    font-size: 0.86rem;
    letter-spacing: 0.18em;
    color: var(--ink-dim-2);
    margin-bottom: 0.6em;
  }

  /* row-reverse + flex-start packs the discs from the right edge inward, so
     the first swatch in the markup is the rightmost one — matching how the
     rest of the panel reads. */
  .ci-swatches {
    display: flex;
    flex-direction: row-reverse;
    justify-content: flex-start;
    align-items: flex-end;
    gap: 1.4rem;
  }

  .ci-swatch {
    display: block;
    height: clamp(64px, 5.6vw, 84px);
    padding: 0;
    flex: none;
    border: 0;
    background: none;
    cursor: pointer;
    line-height: 0;
    /* Dimmed until chosen or hovered, so the active colour is the one that
       reads as lit rather than all three competing at once. */
    opacity: 0.5;
    filter: saturate(0.7) brightness(0.85);
    transition: opacity 0.35s ease, filter 0.35s ease, transform 0.35s ease;
  }

  .ci-swatch img {
    height: 100%;
    width: auto;
    max-width: none;
    display: block;
    pointer-events: none;
    /* Kills BLUE's baked-in black ground; harmless on the cut-out two. */
    mix-blend-mode: screen;
    filter: drop-shadow(0 4px 7px rgba(0, 0, 0, 0.6));
  }

  .ci-swatch:hover,
  .ci-swatch:focus-visible {
    opacity: 0.85;
    filter: saturate(1) brightness(1);
    transform: translateY(-4px);
    outline: none;
  }

  .ci-swatch.is-active {
    opacity: 1;
    filter: saturate(1.1) brightness(1.12)
            drop-shadow(0 0 10px rgba(230, 205, 138, 0.35));
    transform: translateY(-4px) scale(1.1);
  }

  .ci-note {
    font-family: 'Cormorant Garamond', 'Georgia', serif;
    font-size: clamp(0.68rem, 0.9vw, 0.82rem);
    letter-spacing: 0.4em;
    color: var(--ink-faint);
    margin-top: 2.2em;
    direction: ltr;
    text-align: right;
  }

  /* Hover zoom: a small panel that floats beside the cursor, showing a
     magnified crop of the carpet via background-position/size (see
     assets/js/08-lens.js). left/top are set from JS on every pointermove
     with NO transition on them — a lerped or CSS-eased follow always lags
     a frame or more behind a fast-moving cursor, which read as the box
     chasing the pointer instead of sitting glued to it. Only opacity/scale
     animate, for the open/close. */
  #frame-zoom {
    position: fixed;
    top: 0;
    left: 0;
    width: 260px;
    height: 260px;
    z-index: 50;
    border-radius: 16px;
    border: 2px solid rgba(255, 255, 255, 0.85);
    background-repeat: no-repeat;
    opacity: 0;
    transform: scale(0.96);
    pointer-events: none;
    transition: opacity 0.18s ease, transform 0.18s ease;
    box-shadow: 0 14px 34px rgba(0, 0, 0, 0.45);
  }

  #frame-zoom.visible {
    opacity: 1;
    transform: scale(1);
  }

  #right-center {
    position: fixed;
    top: 50%;
    right: -1rem;
    transform: translateY(-50%);
    width: clamp(160px, 20vw, 320px);
    z-index: 1;
    pointer-events: auto;
    perspective: 800px;
  }

  #right-center svg {
    display: block;
    width: 100%;
    height: auto;
  }

  /* The tilt happens on the inner <svg>, so it never fights the host's own
     centring transform. */
  #corner-top-left svg,
  #right-center svg {
    transform-style: preserve-3d;
    will-change: transform;
    filter: drop-shadow(6px 10px 10px rgba(0, 0, 0, 0.55));
  }


  /* Centre stage: the logo and the company name under it. Sized off the
     viewport's short side so the whole lockup always clears the nav line
     above and the scroll hint below. */
  #centre-stage {
    position: fixed;
    top: 56%;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: clamp(1.4rem, 3.4vh, 3rem);
    pointer-events: none;
  }

  #logo-mark {
    width: clamp(120px, 26vh, 260px);
    pointer-events: auto;
    perspective: 800px;
  }

  #logo-mark svg {
    display: block;
    width: 100%;
    height: auto;
    transform-style: preserve-3d;
    will-change: transform;
    filter: drop-shadow(6px 10px 10px rgba(0, 0, 0, 0.55));
  }

  /* The cloth canvas hangs past the logo's box on every side, so folds
     lifting off the edge are not clipped. */
  #logo-mark { position: relative; }

  #logo-mark .cloth-out {
    position: absolute;
    pointer-events: none;
    z-index: 1;
    filter: drop-shadow(6px 10px 10px rgba(0, 0, 0, 0.55));
  }

  /* The name fades up once the logo has settled. White, not gold — the
     ornaments already carry the gold, so the name reads as the bright thing
     against them rather than competing. Three tiers, as in the lockup:
     latin name, kicker, then the arabic line. */
  #wordmark {
    text-align: center;
    opacity: 0;
    margin-top: clamp(1.2rem, 3vh, 3rem);
  }

  .wm-row {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: clamp(0.6rem, 1.4vw, 1.4rem);
  }

  .wm-row-sub { margin-top: 0.9em; }

  .wm-text {
    /* A latin display serif for the latin lines — Amiri's own latin is a
       secondary design and reads flat at this size. Amiri still sets the
       arabic below, where it belongs. */
    font-family: 'Cormorant Garamond', 'Georgia', serif;
    font-weight: 500;
    font-size: clamp(1.6rem, 3.4vw, 3.2rem);
    letter-spacing: 0.2em;
    text-indent: 0.2em; /* offsets the trailing letter-space, keeps it centred */
    line-height: 1.2;
    color: var(--ink-brightest);
    text-shadow: 3px 5px 6px rgba(0, 0, 0, 0.55);
    white-space: nowrap;
  }

  .wm-kicker {
    font-family: 'Cormorant Garamond', 'Georgia', serif;
    font-weight: 400;
    font-size: clamp(0.62rem, 1.2vw, 1.05rem);
    /* Uppercased in CSS rather than in the markup: the line mixes cased and
       all-caps words, which at this letter-spacing reads as two labels stuck
       together instead of one. */
    text-transform: uppercase;
    letter-spacing: 0.46em;
    text-indent: 0.46em;
    margin-top: 0.8em;
    /* Dimmed via colour, not opacity — the reveal animates opacity to 1 and
       would otherwise wash this tier back to full brightness. */
    color: var(--ink-mid-2);
    white-space: nowrap;
  }

  .wm-sub {
    font-family: 'Amiri', 'Georgia', serif;
    font-size: clamp(0.85rem, 1.5vw, 1.2rem);
    letter-spacing: 0.06em;
    color: var(--gold-2);
    text-shadow: 3px 5px 6px rgba(0, 0, 0, 0.55);
    white-space: nowrap;
  }

  /* A diamond with a line tapering away from it — the same motif as the nav
     hover mark. Diamond is the ::before, the line the element itself. */
  .flourish {
    position: relative;
    display: block;
    width: clamp(38px, 7vw, 110px);
    height: 1px;
    /* Fades in from the outside, solid at the inboard end where the diamond
       sits. .flip mirrors the whole thing for the other side, so the diamond
       stays next to the text on both. */
    background: linear-gradient(90deg, rgba(var(--gold-2-rgb), 0), var(--gold-2) 85%);
  }

  .flourish.sm { width: clamp(24px, 4.5vw, 68px); }

  .flourish::before {
    content: '';
    position: absolute;
    right: -3px;
    top: 50%;
    width: 6px;
    height: 6px;
    margin-top: -3px;
    background: var(--gold-5);
    transform: rotate(45deg);
    box-shadow: 0 0 10px rgba(230, 205, 138, 0.8);
  }

  .flourish.sm::before {
    width: 5px;
    height: 5px;
    margin-top: -2.5px;
    right: -2.5px;
  }

  /* Mirrored so both diamonds sit inboard, pointing at the name. */
  .flourish.flip { transform: scaleX(-1); }

  /* Two lower_flower copies straddling the bent hint line: one standing on
     it, one mirrored below. Both are placed by JS, since they hang off the
     line's live geometry rather than a fixed viewport position. */
  .hint-flower {
    position: fixed;
    z-index: -1;
    pointer-events: none;
    opacity: 0;
    transform-origin: center center;
  }

  /* Far-edge fade (away from the anchor dot) lives on the inner svg. The
     reveal sweep lives on the host (below) as a second, independent mask —
     so the two never fight over one mask-image list. */
  .hint-flower svg {
    display: block;
    width: 100%;
    height: auto;
    -webkit-mask-image: linear-gradient(to top, #000 55%, transparent 95%);
    mask-image: linear-gradient(to top, #000 55%, transparent 95%);
    filter: drop-shadow(6px 10px 10px rgba(0, 0, 0, 0.55));
  }
  .hint-flower.flip { transform: scaleY(-1); }

  /* Reveal sweep: a hard diagonal edge that slides across as the flower
     "uncovers", following the same up-left direction the bend line arrives
     from. --reveal 0 = fully hidden, 1 = fully shown. */
  .hint-flower-reveal {
    --reveal: 0;
    -webkit-mask-image: linear-gradient(-45deg, #000 calc(var(--reveal) * 160% - 30%), transparent calc(var(--reveal) * 160% - 30%));
    mask-image: linear-gradient(-45deg, #000 calc(var(--reveal) * 160% - 30%), transparent calc(var(--reveal) * 160% - 30%));
  }

  /* State 2 pans the whole lockup up by one screen. Everything inside is
     position:fixed, so there is no document to scroll — one transform on
     this wrapper is the scroll. The bg canvas sits outside it: a starfield
     is a backdrop, not part of the page that moves. */
  #page {
    position: fixed;
    inset: 0;
    will-change: transform;
  }

  #link-layer {
    position: fixed;
    inset: 0;
    width: 100vw;
    height: 100vh;
    z-index: 1;
    pointer-events: none;
    overflow: visible;
  }

  /* Gradient <stop> colours can't take an inline CSS var() on stop-color in
     all browsers reliably, so the stops carry a class instead and pick up
     the theme colour here — same source of truth as everything else. */
  .theme-gold-2 { stop-color: var(--gold-2); }
  .theme-gold-hover { stop-color: var(--gold-hover); }
  .theme-gold-pale-2 { stop-color: var(--gold-pale-2); }
  .theme-gold-pale { stop-color: var(--gold-pale); }
  .theme-gold-cream { stop-color: var(--gold-cream); }
  .theme-sheen-bright { stop-color: var(--sheen-bright); }

  #link-layer path {
    fill: none;
    stroke: var(--gold-2);
    stroke-width: 1;
    stroke-linecap: round;
    stroke-linejoin: round;
  }

  #link-top-left-to-right-center {
    stroke: url(#link-gradient);
  }

  #spark {
    opacity: 0;
  }

  #nav-menu {
    position: fixed;
    top: 0;
    left: 0;
    z-index: 2;
    display: flex;
    gap: clamp(2rem, 5vw, 4.5rem);
    opacity: 0;
    pointer-events: none;
  }

  #nav-menu a {
    position: relative;
    font-family: 'Amiri', 'Georgia', serif;
    font-size: clamp(1.05rem, 1.4vw, 1.35rem);
    letter-spacing: 0.06em;
    color: var(--gold-nav);
    text-decoration: none;
    white-space: nowrap;
    padding: 1.1rem 0.9rem 0.55rem;
    margin-top: -0.95rem;
    pointer-events: auto;
    text-shadow: 2px 3px 4px rgba(0, 0, 0, 0.55);
    transition: color 0.5s ease, text-shadow 0.5s ease, letter-spacing 0.5s ease;
  }

  #nav-menu a::after {
    content: '';
    position: absolute;
    left: 50%;
    bottom: -0.55rem;
    width: 150%;
    aspect-ratio: 962 / 133;
    transform: translateX(-50%);
    background: linear-gradient(90deg, var(--gold-6), var(--gold-pale) 50%, var(--gold-6));
    -webkit-mask: url('../svg/bottom_item.svg') center / 100% 100% no-repeat;
    mask: url('../svg/bottom_item.svg') center / 100% 100% no-repeat;
    clip-path: inset(0 50% 0 50%);
    opacity: 0;
    filter: drop-shadow(0 0 6px rgba(233, 193, 110, 0.75));
    transition: clip-path 0.6s cubic-bezier(0.22, 0.61, 0.36, 1),
                opacity 0.15s ease;
  }

  #nav-menu a::before {
    content: '';
    position: absolute;
    left: 50%;
    bottom: -0.28rem;
    width: 4px;
    height: 4px;
    margin-left: -2px;
    background: var(--gold-2);
    transform: rotate(45deg) scale(0);
    transition: transform 0.5s cubic-bezier(0.34, 1.4, 0.64, 1) 0.08s,
                box-shadow 0.5s ease;
  }

  #nav-menu a:hover {
    color: var(--gold-pale);
    letter-spacing: 0.1em;
    text-shadow: 2px 3px 4px rgba(0, 0, 0, 0.55);
  }

  #nav-menu a:hover::after {
    clip-path: inset(0 0 0 0);
    opacity: 1;
  }

  #nav-menu a:hover::before {
    transform: rotate(45deg) scale(1);
    box-shadow: 0 0 8px rgba(var(--gold-2-rgb), 0.9);
  }

  @media (prefers-reduced-motion: reduce) {
    #nav-menu a,
    #nav-menu a::after,
    #nav-menu a::before { transition-duration: 0.01s; }
  }

  /* ---- State 4: the contact screen -----------------------------------
     Two screens down. Same fixed + `top: 200vh` idiom #frame-wrap uses, so
     the one #page transform that pans states 1-3 brings this into view too
     — no second scroll mechanism. Children fade/slide in on their own via
     showContact()'s stagger in 07-states.js, matching #carpet-info. */
  #contact {
    position: fixed;
    top: 200vh;
    left: 0;
    width: 100%;
    height: 100vh;
    z-index: 3;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    /* The thread's horizontal run crosses this block at 42vh (see
       contactTarget() in 06-hint.js) - the eyebrow and title sit above that
       line, the details below it. Starting high enough that the title clears
       the run, with .ct-gap holding the band open for the line itself. */
    padding: 16vh 6vw 4vh;
    text-align: center;
    /* Only the links and the map take pointer events; the rest is text and
       must not swallow the wheel gestures that drive the states. */
    pointer-events: none;
  }

  #contact > * {
    opacity: 0;
    transform: translateY(18px);
  }

  #contact .ci-rule { justify-content: center; }

  /* The empty band the thread's horizontal run passes through. A spacer
     rather than a margin on either neighbour, so it stays a stagger step of
     its own and the gap is one number to keep in sync with contactTarget()'s
     0.42 - the run sits at its middle. */
  .ct-gap {
    height: 12vh;
    flex: none;
  }

  .ct-eyebrow {
    font-family: 'Amiri', 'Georgia', serif;
    font-size: clamp(0.85rem, 1.15vw, 1.05rem);
    letter-spacing: 0.3em;
    color: var(--ink-dim);
  }

  .ct-title {
    font-family: 'Amiri', 'Georgia', serif;
    font-weight: 700;
    font-size: clamp(1.6rem, 2.8vw, 2.6rem);
    line-height: 1.3;
    color: var(--ink-brightest);
    margin: 0.35em 0 0;
    text-shadow: 3px 5px 6px rgba(0, 0, 0, 0.55);
  }

  /* Map on one side, the details list on the other. minmax(0,·) on both
     tracks so the map's iframe can't push the row wider than the screen. */
  .ct-grid {
    display: grid;
    grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
    gap: clamp(1.5rem, 4vw, 3.5rem);
    align-items: center;
    width: min(78rem, 100%);
    margin-top: 1.4em;
  }

  .ct-map {
    position: relative;
    aspect-ratio: 16 / 10;
    max-height: 34vh;
    border: 1px solid rgba(var(--gold-2-rgb), 0.35);
    border-radius: 3px;
    overflow: hidden;
    pointer-events: auto;
    box-shadow: 0 14px 34px rgba(0, 0, 0, 0.45);
  }

  .ct-map iframe {
    width: 100%;
    height: 100%;
    border: 0;
    display: block;
    /* OSM's tiles ship bright; knocking them back to the page's own gold-on
       -dark register keeps the map from reading as a pasted-in white card. */
    filter: invert(0.9) hue-rotate(175deg) saturate(0.55) brightness(0.92);
  }

  .ct-rows {
    margin: 0;
    text-align: right;
    display: grid;
    gap: 0.9rem;
  }

  .ct-row {
    border-top: 1px solid rgba(var(--gold-2-rgb), 0.28);
    padding-top: 0.55rem;
  }

  .ct-row dt {
    font-family: 'Amiri', 'Georgia', serif;
    font-size: 0.86rem;
    letter-spacing: 0.18em;
    color: var(--ink-dim-2);
    margin-bottom: 0.3em;
  }

  .ct-row dd {
    font-family: 'Amiri', 'Georgia', serif;
    font-size: clamp(0.95rem, 1.15vw, 1.12rem);
    line-height: 1.7;
    color: var(--ink-bright);
    margin: 0;
  }

  .ct-row a, .ct-social a, .ct-footer a {
    color: var(--ink-bright);
    text-decoration: none;
    pointer-events: auto;
    transition: color 260ms ease;
  }

  .ct-row a:hover, .ct-footer a:hover { color: var(--gold-hover); }

  .ct-social {
    display: flex;
    flex-direction: row-reverse;
    gap: 0.7rem;
    margin-top: 1.6em;
  }

  .ct-social a {
    font-family: 'Amiri', 'Georgia', serif;
    font-size: clamp(0.9rem, 1.1vw, 1.05rem);
    color: var(--ink-soft);
    border: 1px solid rgba(var(--gold-2-rgb), 0.28);
    border-radius: 2px;
    padding: 0.4em 1.4em;
  }

  .ct-social a:hover {
    color: var(--ink-brightest);
    border-color: var(--gold-2);
    background: rgba(var(--gold-2-rgb), 0.12);
  }

  .ct-footer {
    font-family: 'Cormorant Garamond', 'Georgia', serif;
    font-size: clamp(0.72rem, 0.95vw, 0.88rem);
    letter-spacing: 0.22em;
    color: var(--ink-faint);
    direction: ltr;
    margin-top: auto;
    padding-top: 2em;
  }

  .ct-footer a {
    color: var(--gold-2);
    border-bottom: 1px solid rgba(var(--gold-2-rgb), 0.4);
  }

  .ct-heart { color: var(--accent-heart); letter-spacing: normal; }

  /* Stacks on narrow screens; the map keeps its own height so it doesn't
     collapse to a sliver above the details. */
  @media (max-width: 780px) {
    .ct-grid { grid-template-columns: minmax(0, 1fr); }
    .ct-map { max-height: 22vh; }
    #contact { padding-top: 30vh; }
  }

  /* The state-4 flower is the thread's terminus in front of the contact
     block's backdrop, not a decoration tucked behind the page like the
     state-1 pair — so it opts out of .hint-flower's z-index:-1. Sized
     larger too, so it reads as the bloom the whole thread opens into. */
  #contact-flower {
    z-index: 2;
  }
