/*
 * globe.css
 *
 * The globe lives inside #globe-stage and knows nothing about the window. The
 * canvas fills that container, and globe.js measures the container (not the
 * viewport) to size its drawing buffer and to fit the planet. Resizing the
 * stage by any means, whether these variables, a parent layout, a flex or grid
 * cell, or a JavaScript style change, is enough on its own: the canvas follows
 * it and the planet refits.
 *
 * The two variables below are the intended way to shrink the stage later. They
 * are the only place its size is decided.
 *
 * The dashboard panels deliberately do not touch them. They are laid over the
 * page rather than beside it, so that showing and hiding them cannot resize the
 * stage, and therefore cannot resize the planet or discard a zoom that was set
 * by hand. See the head of panels.css.
 */

/*
 * The palette and the two typefaces are the resume's, so the dashboard reads as
 * part of the same site rather than as a separate thing that happens to be
 * hosted next to it. The values are copied from assets/css/main.css one level
 * up, where they were sampled off a reference PCB photograph rather than picked
 * by eye. They are duplicated here rather than shared because this directory is
 * self-contained by design: it has no build step, no dependencies, and is meant
 * to run wherever it is dropped. If the resume's greens ever move, these are the
 * ones to move with them.
 */
:root {
    --globe-stage-width: 100vw;
    --globe-stage-height: 100vh;

    --page-background-colour: #000000;

    --bg-panel: #1c1815;
    --bg-elevated: #2c2724;

    --accent-primary: #00b060;
    --accent-secondary: #17c96b;
    --accent-dim: #045c28;

    --text-primary: #d7ecd3;
    --text-secondary: #9fcf9a;

    --border-subtle: #0e6b2c;

    --font-mono: 'JetBrains Mono', Consolas, 'Courier New', monospace;
    --font-console: 'VT323', Consolas, monospace;
}

/*
 * Both faces are served from this directory rather than from Google, which is
 * where the resume gets them. The resume is a page on a website; this is a
 * self-contained folder that is supposed to run from a copy with no network,
 * and a hotlinked font would quietly break that the first time it was opened
 * offline. JetBrains Mono is shipped as its variable file, which is why one
 * woff2 covers the whole 400 to 700 range.
 *
 * swap rather than block: the globe is the point of the page and should not
 * wait on typography to appear.
 */
@font-face {
    font-family: 'JetBrains Mono';
    font-style: normal;
    font-weight: 400 700;
    font-display: swap;
    src: url('../fonts/jetbrains-mono-400-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+2074, U+20AC, U+2122,
        U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/*
 * There is no Latin Extended face here, and that was checked rather than
 * assumed. The country names carry plenty of diacritics, but every one of them
 * is inside Latin-1 and so inside the block above: Côte d'Ivoire, Curaçao,
 * São Tomé and Åland all live under U+00FF. Nothing in the data set reaches
 * past it, so a Latin Extended file would be 11 KB that never renders a glyph.
 *
 * If the borders are ever rebuilt from a finer Natural Earth set and a name
 * does reach past U+00FF, that word will fall back mid-label to whatever the
 * system offers, which shows as a change of typeface inside one name. The fix
 * then is one more woff2 and one more block, not a change to anything else.
 */
@font-face {
    font-family: 'VT323';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('../fonts/vt323-400-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+2074, U+20AC, U+2122,
        U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

html,
body {
    margin: 0;
    padding: 0;
    width: 100%;
    height: 100%;
    background-color: var(--page-background-colour);
    overflow: hidden;
}

/*
 * The stage is the box the planet is fitted into. It is positioned so that the
 * absolutely positioned canvas inside it resolves against the stage rather than
 * against the page, and it hides overflow so that nothing the canvas draws can
 * ever escape the box.
 */
#globe-stage {
    position: relative;
    width: var(--globe-stage-width);
    height: var(--globe-stage-height);
    overflow: hidden;
    background-color: var(--page-background-colour);
}

/*
 * The canvas is taken out of the normal flow so that it can never introduce a
 * scrollbar, and so that the browser never adds the few pixels of inline layout
 * leading that an inline level canvas would otherwise get. Its width and height
 * attributes are set in JavaScript; these CSS rules only stretch the element to
 * the stage.
 */
#globe-canvas {
    position: absolute;
    top: 0;
    left: 0;
    display: block;
    width: 100%;
    height: 100%;

    /*
     * touch-action: none stops a touch drag being taken by the browser as a
     * scroll or a pinch before the page ever sees it, which would make the
     * globe unusable on a touchscreen. The two user-select rules stop a mouse
     * drag turning into a text selection of the rest of the page.
     */
    touch-action: none;
    -webkit-user-select: none;
    user-select: none;
    cursor: grab;
}

/*
 * Added by globe.js for the duration of a drag. Purely the standard pointer
 * feedback for a draggable object; nothing about the rendering depends on it.
 */
#globe-canvas.is-dragging {
    cursor: grabbing;
}

/*
 * Labels
 *
 * Country and body names are HTML sitting over the canvas rather than glyphs
 * drawn into it. Text in WebGL means either a glyph atlas and a quad per
 * character or signed distance fields, and both of those are a lot of machinery
 * to end up with worse type than the browser sets for free. The browser also
 * hints and subpixel-renders at whatever the device pixel ratio happens to be,
 * which a texture atlas baked at one size does not.
 *
 * The cost is that labels cannot be occluded by the globe's depth buffer, so
 * globe.js hides them by geometry instead: a label whose point has turned away
 * from the camera is not drawn.
 *
 * The layer takes no pointer events at all, so dragging and scrolling still
 * reach the canvas underneath even with the cursor over a name.
 */
#globe-labels {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    overflow: hidden;
    pointer-events: none;
}

/*
 * Each label is positioned by a transform rather than by top and left, so that
 * moving it is a compositor operation and does not invalidate layout. The
 * translate(-50%, -50%) centres the box on its point, and globe.js appends the
 * pixel translation in front of it every frame.
 *
 * The text shadow is doing real work rather than decoration: names sit over
 * coastlines, ice, cloud and open ocean within a single frame, and a plain
 * light glyph disappears against the bright side of the planet.
 */
.globe-label {
    position: absolute;
    top: 0;
    left: 0;
    display: none;
    white-space: nowrap;
    font-family: var(--font-mono);
    font-size: 11px;
    letter-spacing: 0.06em;
    color: var(--accent-secondary);

    /*
     * The same trace green as the body names, so everything written over the
     * scene is one voice.
     *
     * The black layers underneath are doing more work here than they do on the
     * body names, and are kept at full strength rather than traded for more
     * glow. A country name is 11px, it sits directly on the surface rather than
     * out in empty space, and in one frame it can cross bright desert, ice,
     * cloud and dark ocean. Green on sunlit sand is the hard case, and what
     * saves it is the dark halo, not the colour.
     */
    text-shadow:
        0 0 3px rgba(0, 0, 0, 0.95),
        0 0 6px rgba(0, 0, 0, 0.85),
        0 0 10px rgba(0, 0, 0, 0.65),
        0 0 14px rgba(0, 176, 96, 0.3);
    will-change: transform, opacity;
}

/*
 * Ocean names.
 *
 * A map has always drawn water differently from land, and the reason is not
 * decoration: the names of the seas sit over an area rather than over a place,
 * and if they are set the same as the countries they read as one more country.
 * The convention is lighter, wider and quieter, which is what this is: the pale
 * mint the resume uses for secondary text instead of the borders' trace green,
 * tracked well out, and small.
 *
 * Not uppercase, because at this size and this tracking the words are already
 * doing enough and full capitals in a monospace face read as a heading.
 */
.globe-label.is-ocean {
    font-size: 11px;
    letter-spacing: 0.28em;
    color: var(--text-secondary);
}

/*
 * The numbers on the grid.
 *
 * These are the grid's own colour, the primary trace green rather than the
 * brighter secondary the country names use, so they read as part of the lines
 * they are numbering rather than as more names on the map. Smaller than
 * anything else on the globe as well: a scale should be readable when it is
 * looked for and invisible when it is not.
 */
.globe-label.is-grid-value {
    font-size: 9px;
    letter-spacing: 0.05em;
    color: var(--accent-primary);
}

/*
 * The three bodies get the console face, at a larger size and letterspaced
 * wide. The resume reserves VT323 for short atmospheric labels where the retro
 * console feel matters more than reading at length, and SOL, TERRA and LUNA are
 * exactly that: three words, never read as prose, and wanting to look like
 * something a terminal printed rather than something a map did.
 */
.globe-label.is-body {
    font-family: var(--font-console);
    font-size: 20px;
    letter-spacing: 0.34em;
    text-transform: uppercase;
    color: var(--accent-secondary);
    text-shadow:
        0 0 4px rgba(0, 0, 0, 0.95),
        0 0 10px rgba(0, 176, 96, 0.35);
}

/*
 * Controls
 *
 * Top middle, above the canvas and above the labels. Deliberately plain: this
 * is the minimum that is legible over a black sky and a bright planet, and the
 * look is not meant to be the final word on it.
 *
 * The row is a sibling of the stage rather than a child of it, and fixed to the
 * page rather than positioned inside it. It is chrome for the whole dashboard
 * now, not a label on the globe: one of its buttons operates the panels, which
 * are not in the stage either. It also cannot be clipped there. The stage hides
 * its overflow, so any future change that makes the stage smaller than the
 * window would cut the ends off the row, and that is a trap worth not leaving
 * set. Nothing in globe.js looks the buttons up through the stage, so the move
 * costs nothing.
 */
/*
 * The row is a measured bar rather than a huddle of buttons at whatever width
 * their labels happen to come to: the full page width on a screen higher than
 * it is wide, and a third of the page - the same third the globe sits in
 * between the panel rails - on a screen wider than it is high. The buttons are
 * equal columns of it rather than each its own size, so the bar reads as one
 * object.
 *
 * box-sizing is set here because this file does not carry a global reset and
 * the width is now stated in viewport units. Without it the padding and the
 * border would be added on top of 100vw and the bar would be wider than the
 * page it is meant to fit.
 */
#globe-controls {
    box-sizing: border-box;
    position: fixed;
    top: 16px;
    left: 50%;
    transform: translateX(-50%);
    width: 100vw;

    display: grid;
    grid-template-columns: repeat(2, 1fr);

    gap: 8px;
    padding: 8px;
    background-color: var(--bg-panel);

    /*
     * The dashboard panels' border rather than a heavier one of its own: the
     * same 2px of dim green and the same small radius, on the same panel
     * background. The bar sits directly above the middle column with a rail of
     * panels either side of it, so anything else made it the loudest object on
     * a page it is not the subject of. panels.css also lines its top edge up
     * with the top of those rails.
     */
    border: 2px solid var(--accent-dim);
    border-radius: 4px;
    z-index: 2;
}

/*
 * Four across needs about 107px a button for the longest label at full size.
 * A phone held upright cannot give that to four columns and can to two, so the
 * base above is two by two and one row is the exception rather than the rule.
 */
@media (min-width: 600px) {
    #globe-controls {
        grid-template-columns: repeat(4, 1fr);
    }
}

/*
 * Wider than high: the bar is a third of the page. A third of a 1600px screen
 * is 533px, which is comfortable for four; a third of a 1280px screen is 427px,
 * which is not at 14px type. The label size therefore tracks the viewport
 * between 9 and 14px so that four columns keep fitting all the way down, and
 * below about 1100px, where even 9px would not fit, the bar goes to two by two
 * instead and the type goes back up.
 */
@media (min-aspect-ratio: 1/1) {
    #globe-controls {
        width: 33.3333vw;
        grid-template-columns: repeat(4, 1fr);
    }
}

@media (min-aspect-ratio: 1/1) and (max-width: 1100px) {
    #globe-controls {
        grid-template-columns: repeat(2, 1fr);
    }
}

/*
 * The way back to the resume.
 *
 * A row of its own across the foot of the bar rather than a fifth button: five
 * columns in a bar a third of the page wide leaves 97px each, and the longest
 * label needs 107px at this size, so the buttons would all have had to shrink to
 * make room for something that is not one of them. It is a link and not a
 * control, and it reads as one.
 *
 * Spanning every column works for both bars, the four across and the two by two,
 * without either of them needing to know it is there.
 *
 * The guillemet rather than a left arrow because the font is subset to Latin-1
 * and U+2190 is not in it: the arrow would fall back mid-line to whatever the
 * system offers, which shows as one character in a different typeface.
 */
#globe-home-link {
    grid-column: 1 / -1;
    padding: 3px 4px 0 4px;
    text-align: center;
    color: var(--text-secondary);
    font-family: var(--font-mono);
    font-size: 12px;
    letter-spacing: 0.08em;
    text-decoration: none;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    transition: color 120ms ease;
}

#globe-home-link:hover {
    color: var(--accent-secondary);
}

#globe-home-link:focus-visible {
    outline: 2px solid var(--accent-secondary);
    outline-offset: 2px;
}

/*
 * The resume's control style: panel background, a solid green rule around it, a
 * small radius, monospace, letterspaced. The heavy visible border is the whole
 * look, so it is not softened here into the translucent hairline that a
 * floating overlay would normally get.
 */
.globe-control {
    appearance: none;
    -webkit-appearance: none;
    margin: 0;
    padding: 6px 14px;
    background-color: #0c1f12;
    border: 2px solid var(--border-subtle);
    border-radius: 4px;
    color: var(--accent-secondary);
    font-family: var(--font-mono);
    font-size: 14px;
    letter-spacing: 0.12em;
    line-height: 1.2;
    white-space: nowrap;
    cursor: pointer;
    transition: background-color 120ms ease, border-color 120ms ease, color 120ms ease;
}

/*
 * The label size in the landscape bar, and the reason it is not a fixed number.
 * The bar is a third of the page there, so the space a button gets is decided
 * by the screen rather than by the label, and the longest of them - Lat / Lon -
 * needs about 6.5 times the font size in width plus its padding. Tracking the
 * viewport keeps four columns fitting from a 1600px screen, where there is room
 * for the full 14px, down to about 1100px, where the two by two rule below
 * takes over and the type can go back up.
 *
 * These two blocks sit after the base rule rather than beside their layout
 * partners above, because they set the same property at the same specificity
 * and the last one written is the one that applies.
 */
@media (min-aspect-ratio: 1/1) {
    .globe-control {
        padding: 6px 8px;
        font-size: clamp(9px, 0.85vw, 14px);
    }
}

@media (min-aspect-ratio: 1/1) and (max-width: 1100px) {
    .globe-control {
        font-size: 13px;
    }
}

.globe-control:hover:not(:disabled) {
    background-color: var(--bg-elevated);
    border-color: var(--accent-primary);
    color: var(--text-primary);
}

.globe-control:focus-visible {
    outline: 2px solid var(--accent-secondary);
    outline-offset: 2px;
}

/*
 * A toggle that is currently on. aria-pressed is the state, and the styling
 * hangs off it rather than off a separate class, so the two cannot disagree.
 *
 * On reads as a lit trace: the bright green promoted from the text to the
 * border and a glow behind it, rather than a filled block, which would fight
 * the panel it sits in.
 */
.globe-control[aria-pressed="true"] {
    background-color: var(--accent-dim);
    border-color: var(--accent-primary);
    color: var(--text-primary);
    box-shadow: 0 0 8px rgba(0, 176, 96, 0.45);
}

/*
 * Nothing in the row is disabled any more, now that the panels button does
 * something. The rule stays for the next control that has to wait for the thing
 * it operates: unavailable should look unavailable rather than broken.
 */
.globe-control:disabled {
    opacity: 0.34;
    cursor: not-allowed;
}
