@font-face {
    font-family: 'IM Fell English';
    font-style: italic;
    font-weight: 400;
    /* Preloaded: a short wait for the name's own face, then a stand-in rather than no text. */
    font-display: fallback;
    src: url('/fonts/im-fell-english-latin-400-italic.woff2') format('woff2');
}
@font-face {
    font-family: 'IM Fell English SC';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('/fonts/im-fell-english-sc-latin-400-normal.woff2') format('woff2');
}

:root {
    --table: #2b1e14;
    --vellum: #ece0c4;
    --ink: #2a1f16;
    --ink-soft: #4a3a2a;
    --red: #852619;
    --red-light: #d48a70;
    --green: #3f6a48;
    --green-light: #86a27f;
    /* Verdigris deep enough for text on the halo (4.8:1); the chart's lines keep --green. */
    --green-text: #3a6243;
    --gold: #c39543;
    --vellum-deep: #d9c49c;
    /* The open hide's own tone, for haloes that shouldn't read as paler patches. */
    --vellum-halo: #e0d5bd;
    --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
    /* A pen drawing a long line: off at once, then a steady hand to the end. */
    --pen: cubic-bezier(0.25, 0.6, 0.35, 1);
    --pen-edge: linear-gradient(100deg, #000 48%, transparent 52%);
    --fell: 'IM Fell English', 'Iowan Old Style', 'Palatino Linotype', Palatino, Georgia, serif;
    --fell-sc: 'IM Fell English SC', 'IM Fell English', 'Iowan Old Style', Georgia, serif;
    --safe-top: env(safe-area-inset-top, 0px);
    --safe-right: env(safe-area-inset-right, 0px);
    --safe-bottom: env(safe-area-inset-bottom, 0px);
    --safe-left: env(safe-area-inset-left, 0px);
    /* One chart unit in CSS px: the chart is 1600 x 1000 units, fitted inside the viewport. */
    --u: min(calc(100vw / 1600), calc(100vh / 1000));
}
@supports (height: 100dvh) {
    :root { --u: min(calc(100vw / 1600), calc(100dvh / 1000)); }
}

html, body {
    margin: 0;
    height: 100%;
    overflow: hidden;
    background: var(--table);
    color: var(--ink);
    font-family: var(--fell);
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;
    overscroll-behavior: none;
}
::selection { background: var(--red); color: var(--vellum); }

/* The table: walnut, lit from above so its edges fall into shadow. */
body {
    background:
        radial-gradient(ellipse 75% 70% at 50% 46%, rgb(14 9 5 / 0) 35%, rgb(14 9 5 / 0.62) 100%),
        url('/walnut.webp') 0 0 / 1100px repeat,
        var(--table);
}

main { position: fixed; inset: 0; touch-action: manipulation; }
/* The drawing's lettering is part of the picture; only the title is text to select. */
.chart, .wind { -webkit-user-select: none; user-select: none; }

/* The chart: the whole hide, fitted to the screen. On a portrait screen it turns a quarter, neck
   down, the way a chart is turned on the table to read it. */
.chart {
    position: absolute;
    left: 50%;
    top: 50%;
    width: 100vw;
    height: 100vh;
    height: 100dvh;
    transform: translate(-50%, -50%);
    display: block;
}
.clear-port { display: none; }
.clear-text { fill: #000; stroke: #000; stroke-linejoin: round; }
/* Partly black: the net fades around the lockup rather than vanishing in a box. */
.ko-field { fill: #000; fill-opacity: 0.6; stroke: none; }
.knocked-out .clear-land, .knocked-out .clear-port { display: none; }
/* Text for screen readers only. */
.visually-hidden { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
/* chart.js (and the checks) measure each title line's baseline with one of these, then remove it. */
.baseline-probe { display: inline-block; width: 0; height: 0; vertical-align: baseline; }

/* Under the vellum plate: the same shape in flat vellum, carrying the shadow onto the table, and
   standing in for the plate if it ever fails to load. */
.hide-ground { fill: var(--vellum-deep); }

/* The net: hairlines that recede behind the coasts and roses, as on the charts. */
.ray { fill: none; stroke-width: 0.7px; vector-effect: non-scaling-stroke; }
.ray.w { stroke: var(--ink); stroke-opacity: 0.24; }
.ray.h { stroke: var(--green); stroke-opacity: 0.28; }
.ray.q { stroke: var(--red); stroke-opacity: 0.21; }
/* Ink life: each ray a little fuller or thinner, as the pen was refilled. */
.ray.w.v0 { stroke-opacity: 0.18; } .ray.w.v2 { stroke-opacity: 0.28; } .ray.w.v3 { stroke-opacity: 0.32; }
.ray.h.v0 { stroke-opacity: 0.21; } .ray.h.v2 { stroke-opacity: 0.32; } .ray.h.v3 { stroke-opacity: 0.36; }
.ray.q.v0 { stroke-opacity: 0.15; } .ray.q.v2 { stroke-opacity: 0.25; } .ray.q.v3 { stroke-opacity: 0.28; }
.ring { fill: none; stroke: var(--ink); stroke-opacity: 0.32; stroke-width: 0.6px; vector-effect: non-scaling-stroke; }
.minor { fill: var(--red); stroke: var(--ink); stroke-width: 0.6px; vector-effect: non-scaling-stroke; }
.scale-frame { fill: none; stroke: var(--ink); stroke-width: 0.8px; vector-effect: non-scaling-stroke; }
.scale-bar, .scale-dot { fill: var(--ink); }

/* In the chart's own units, not screen pixels, so its dashes (the inking) track the pen exactly. */
.coast { fill: none; stroke: var(--ink); stroke-width: 0.75px; stroke-linejoin: round; stroke-linecap: round; }
.stroke { fill: none; stroke: var(--ink); stroke-linejoin: round; stroke-linecap: round; vector-effect: non-scaling-stroke; }
/* Iron-gall ink, pressed a little harder or lighter: near the net's own weight, never a heavy outline. */
.stroke.k1 { stroke-width: 0.7px; stroke-opacity: 0.6; }
.stroke.k2 { stroke-width: 0.9px; stroke-opacity: 0.7; }
.stroke.k3 { stroke-width: 1.1px; stroke-opacity: 0.78; }
.inscription { font-family: var(--fell); font-style: italic; font-size: 21px; fill: var(--ink-soft); text-anchor: middle; dominant-baseline: central; }
.numerals { font-family: var(--fell-sc); font-style: normal; letter-spacing: 0.06em; }
.names { fill: none; stroke-width: 0.5px; stroke-linecap: round; stroke-linejoin: round; vector-effect: non-scaling-stroke; }
.names.ink { stroke: var(--ink-soft); stroke-opacity: 0.85; }
.names.red { stroke: var(--red); }
.isle { stroke: var(--ink); stroke-width: 0.8px; vector-effect: non-scaling-stroke; }
.isle.red { fill: var(--red); }
.isle.gold { fill: var(--gold); }
.isle.stripes { fill: url(#stripes); }
.stripe-red { fill: var(--red); }
.stripe-gold { fill: var(--gold); }
.rock { stroke: var(--ink); stroke-width: 1.4px; stroke-linecap: round; vector-effect: non-scaling-stroke; }
.shoal { fill: var(--red); }
.pen { fill: var(--ink); opacity: 0; transition: opacity 0.15s ease-out; }
.pen.on { opacity: 1; }

.highlight { fill: none; stroke-width: 2px; stroke-linecap: round; vector-effect: non-scaling-stroke; opacity: 0; transition: opacity 0.4s ease-out; }
.highlight.w { stroke: var(--ink); }
.highlight.h { stroke: var(--green); }
.highlight.q { stroke: var(--red); }
.highlight.on { opacity: 0.9; }

/* A pointed-at rose or star: its own rhumbs inked over the net in their colours, while it's
   pointed at. In chart units (chart.js sets each line's pathLength; the longest is about 1400),
   so with one timing for all the ink runs out from the mark as a ring, a pen point at each head:
   winds first, then half winds, then quarters, as a chartmaker drew them. It waits for the main
   rose's own rhumb to reach the mark, so the rose sends its line out and the mark answers. */
.echo { opacity: 0; transition: opacity 0.45s ease-out; }
.echo.on { opacity: 1; transition-duration: 0.15s; }
/* Held on a layer of its own while it's inked, so each frame repaints the mark's lines alone,
   not the whole net through the title's feathered mask. (A transform, not opacity, so the fade
   out still runs.) */
.motion .echo.on { animation: layer 2.2s linear; }
/* The sweep's passing answer. */
.echo.on.faint { opacity: 0.55; }
.echo path { fill: none; stroke-linecap: round; vector-effect: non-scaling-stroke; }
.echo .w { stroke: var(--ink); }
.echo .h { stroke: var(--green); }
.echo .q { stroke: var(--red); }
.echo .trail { stroke-width: 1px; stroke-opacity: 0.62; }
.echo .trail.q { stroke-opacity: 0.5; }
/* A small star's answer is quieter than a rose's. */
.echo.star .trail { stroke-opacity: 0.5; }
.echo.star .trail.q { stroke-opacity: 0.4; }
.echo .bead { display: none; }
.motion .echo .trail { stroke-dasharray: 1400; animation: echo-trail 1.6s var(--pen) 0.1s both; }
.motion .echo .bead { display: inline; stroke-width: 3.5px; stroke-dasharray: 0.5 4000; animation: echo-bead 1.6s var(--pen) 0.1s both, lift 1.6s linear 0.1s both; }
.motion .echo .h { animation-delay: 0.25s; }
.motion .echo .q { animation-delay: 0.4s; }

.rose-ring { fill: var(--vellum); fill-opacity: 0.82; stroke: var(--ink); stroke-width: 1.2px; vector-effect: non-scaling-stroke; }
.rose-ring.thin { fill: none; stroke-width: 0.7px; }
.rose-tick { stroke: var(--ink); stroke-width: 0.8px; vector-effect: non-scaling-stroke; }
.pt { stroke: var(--ink); stroke-width: 0.6px; stroke-linejoin: round; vector-effect: non-scaling-stroke; }
.pt.q.lit { fill: var(--red-light); }
.pt.q.shade { fill: var(--red); }
.pt.h.lit { fill: var(--green-light); }
.pt.h.shade { fill: var(--green); }
.pt.w.lit { fill: var(--gold); }
.pt.w.shade { fill: var(--ink); }
.pt.north.shade { fill: var(--red); }
.rose-hub { fill: var(--red); stroke: var(--ink); stroke-width: 0.8px; vector-effect: non-scaling-stroke; }
.rose-initial { font-family: var(--fell-sc); font-size: 26px; fill: var(--ink); text-anchor: middle; dominant-baseline: central; }
.fleur { fill: var(--gold); stroke: var(--ink); stroke-width: 0.7px; stroke-linejoin: round; vector-effect: non-scaling-stroke; }

/* The title, set where a chart's title sits, in the clear vellum lower left. */
.title {
    position: absolute;
    left: calc(50vw - 420 * var(--u));
    bottom: calc(50vh - 375 * var(--u));
    bottom: calc(50dvh - 375 * var(--u));
    margin: 0;
}
.wordmark {
    margin: 0;
    font-family: var(--fell);
    font-style: italic;
    font-weight: 400;
    /* A floor for small screens, itself held in proportion to the chart so it can't reach the rose. */
    font-size: max(min(80px, calc(185 * var(--u))), calc(150 * var(--u)));
    line-height: 0.86;
    letter-spacing: -0.01em;
    color: var(--ink);
    /* The italic's x leans past its box; this keeps the inking reveal from clipping it. */
    padding: 0 0.12em 0.12em 0.06em;
    margin-left: -0.06em;
}
.wordmark .x { color: var(--red); }
.soon {
    margin: 0.35em 0 0;
    font-family: var(--fell-sc);
    font-size: max(19px, calc(34 * var(--u)));
    letter-spacing: 0.08em;
    color: var(--red);
}
.line {
    margin: 0.2em 0 0;
    font-style: italic;
    font-size: max(16px, calc(24 * var(--u)));
    color: var(--ink-soft);
}

/* The name of the wind under the pointer, or the one the rose is sweeping through. */
.wind {
    position: absolute;
    left: 0;
    top: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 0;
    pointer-events: none;
    white-space: nowrap;
    opacity: 0;
    transition: opacity 0.2s ease-out;
    paint-order: stroke fill;
    -webkit-text-stroke: max(3.5px, calc(3.5 * var(--u))) var(--vellum-halo);
}
.wind.on { opacity: 1; }
/* Off the skin, on the walnut, the label is written in vellum with no halo. */
.wind.off { -webkit-text-stroke: 0; }
.wind.off .wind-name, .wind.off .wind-way { color: var(--vellum); }
.wind-name { font-style: italic; font-size: max(19px, calc(21 * var(--u))); line-height: 1.05; color: var(--ink); }
.wind-name.h { color: var(--green-text); }
.wind-name.q { color: var(--red); }
.wind-way { font-family: var(--fell-sc); font-size: max(16px, calc(17 * var(--u))); line-height: 1.15; letter-spacing: 0.08em; color: var(--ink-soft); }
.wind .hy { margin: 0 -0.08em; -webkit-text-stroke: 0; } /* its halo would erase the letter before it */

@media (orientation: portrait) {
    :root { --u: min(calc(100vh / 1600), calc(100vw / 1000)); }
    @supports (height: 100dvh) {
        :root { --u: min(calc(100dvh / 1600), calc(100vw / 1000)); }
    }
    .chart {
        width: 100vh;
        width: 100dvh;
        height: 100vw;
        transform: translate(-50%, -50%) rotate(-90deg);
    }
    .clear-land { display: none; }
    .clear-port { display: inline; }
    .title {
        left: 0;
        right: 0;
        bottom: calc(50vh - 480 * var(--u));
        bottom: calc(50dvh - 480 * var(--u));
        text-align: center;
    }
    .wordmark { font-size: max(min(88px, calc(200 * var(--u))), calc(176 * var(--u))); padding: 0 0.08em 0.12em; margin-left: 0; }
    .soon { font-size: max(20px, calc(38 * var(--u))); }
    .inscription { font-size: 32px; }
    .line { font-size: max(16px, calc(27 * var(--u))); }
}

/* On the narrowest portrait screens (a phone zoomed to 150% or more) the title's legible floors
   need room the rose would otherwise take; the rose is drawn a little smaller there. */
@media (orientation: portrait) and (max-width: 300px) {
    .rose { scale: 0.8; }
    .title { bottom: calc(50vh - 462 * var(--u)); bottom: calc(50dvh - 462 * var(--u)); }
}

/* The inking: only when boot.js has found that motion is welcome. */
.anim .ray { stroke-dasharray: 1; stroke-dashoffset: 1; animation: ink 2.2s var(--ease-out) both; }
.anim .coast { stroke-dasharray: 1; stroke-dashoffset: 1; animation-name: ink; animation-timing-function: linear; animation-fill-mode: both; }
.anim .rose { animation: rose 1.3s var(--ease-out) 0.1s both; }
.anim .minor, .anim .satellite, .anim .feature { transform-box: fill-box; transform-origin: center; animation: bloom 0.7s var(--ease-out) both; }
.anim .wordmark, .anim .soon, .anim .line {
    /* Written in: a slanted, soft edge travels across, the way a pen moves along italic letters.
       The mask itself lives in the keyframes, so once written (or if anything goes wrong) the
       letters carry no mask at all. */
    -webkit-mask-size: 200% 100%;
    mask-size: 200% 100%;
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
}
.anim .wordmark { animation: write 1.5s cubic-bezier(0.3, 0.1, 0.5, 1) 1s backwards; }
.anim .names { animation-name: appear; animation-duration: 0.9s; }
.anim .stroke { animation-name: appear; animation-duration: 0.25s; }
.anim .inscription { animation: appear 1.2s ease-out 12.4s both; }
/* The title's knock-out arrives with each line's writing, so the net never shows its outline early. */
/* In four steps, not a fade: each step re-renders the feathered mask over the whole net. */
.anim .ko-field, .anim .ko-wordmark { animation: appear 1.5s steps(4, end) 1s both; }
.anim .ko-soon { animation: appear 0.9s steps(4, end) 2.3s both; }
.anim .ko-line { animation: appear 1.3s steps(4, end) 2.9s both; }
/* Held until chart.js says the vellum, fonts and tab are ready (see boot.js). */
.hold :is(.ray, .coast, .rose, .minor, .satellite, .feature, .scale, .inscription, .wordmark, .soon, .line, .ko-field, .ko-wordmark, .ko-soon, .ko-line) {
    animation-play-state: paused !important;
}
.anim .scale { animation: appear 0.9s ease-out 2.2s both; }
.anim .soon { animation: write 0.9s cubic-bezier(0.3, 0.1, 0.5, 1) 2.3s backwards; }
.anim .line { animation: write 1.3s cubic-bezier(0.3, 0.1, 0.5, 1) 2.9s backwards; }
.motion .highlight.on { stroke-dasharray: 1; animation: ink 0.6s var(--ease-out) both; }

@keyframes ink { from { stroke-dashoffset: 1; } to { stroke-dashoffset: 0; } }
@keyframes rose { from { opacity: 0; transform: scale(0.86) rotate(-11.25deg); } to { opacity: 1; transform: none; } }
@keyframes bloom { from { opacity: 0; transform: scale(0.3); } to { opacity: 1; transform: none; } }
/* The mask is twice the line's width with its soft edge at the middle, so the edge enters at the
   start and leaves at the end; the extra 4% covers the slant. */
@keyframes write {
    from { -webkit-mask-image: var(--pen-edge); mask-image: var(--pen-edge); -webkit-mask-position: 104% 0; mask-position: 104% 0; }
    to { -webkit-mask-image: var(--pen-edge); mask-image: var(--pen-edge); -webkit-mask-position: -4% 0; mask-position: -4% 0; }
}
@keyframes appear { from { opacity: 0; } to { opacity: 1; } }
@keyframes echo-trail { from { stroke-dashoffset: 1400; } to { stroke-dashoffset: 0; } }
/* A pen point at the trail's head (offset 0.5 - 1400 x the eased progress); it lifts at the end. */
@keyframes echo-bead { from { stroke-dashoffset: 0.5; } to { stroke-dashoffset: -1399.5; } }
/* Stroke opacity, not opacity: an opacity animation would give each of the 32 points a layer. */
@keyframes lift { 0%, 70% { stroke-opacity: 0.85; } 100% { stroke-opacity: 0; } }
@keyframes layer { from, to { transform: translate(0, 0); } }


/* High Contrast: the chart's colours can't follow the user's, so it steps aside for the words. */
@media (forced-colors: active) {
    .chart, .wind { display: none; }
    body { background: Canvas; }
    .wordmark, .wordmark .x, .soon, .line { color: CanvasText; }
}

/* Print: the finished chart, its title cleared by the fallback field (screen-placed letters
   don't follow the page onto paper), and nothing interactive. */
@media print {
    .anim *, .anim *::before, .anim *::after { animation: none !important; }
    .anim .ray, .anim .coast { stroke-dasharray: none; stroke-dashoffset: 0; }
    .anim .wordmark, .anim .soon, .anim .line { -webkit-mask-image: none; mask-image: none; }
    .clear-text, .wind, .highlight, .echo, .pen { display: none; }
    .knocked-out .clear-land { display: inline; }
}
@media print and (orientation: portrait) {
    .knocked-out .clear-land { display: none; }
    .knocked-out .clear-port { display: inline; }
}
