Skip to the content

Tsunagi繋ぎ

Join each pair with a separate line and fill the board, through fixed levels or seeded puzzles.

Tsunagi means joining in Japanese. About the name

Contents

Reference · Playable board

TSUNAGI_PLAY_STYLEconstant

THE STYLE a playable Tsunagi board wears (mountTsunagi, <tsunagi-board>): the drawing's own (TSUNAGI_STYLE) and the board's box, its buttons, its lines of words and its zoom pad. Colours are custom properties on .tsunagi-play (--tsp-ink, --tsp-muted, --tsp-rule, --tsp-surface, --tsp-accent, --tsp-good) so a page sets only what it wants different.

Nothing moves when something is chosen: the board is one square box, the lines of words keep the room their longest wording takes, and the buttons are one size. Nothing the player touches can be selected.

Import it

import { TSUNAGI_PLAY_STYLE } from "@johnmorrisdotca/tsunagi/play";

Definition

const TSUNAGI_PLAY_STYLE: "\n.tsunagi {\n  --tsu-paper: #fbf8f1; --tsu-paper-deep: #fbf8f1; --tsu-frame: #a98954; --tsu-grid: #cfc6b2; --tsu-ink: #1f2320;\n  --tsu-coordinate: #5b3d1c; --tsu-shu: #d9381e; --tsu-good: #2f7a4f;\n  --tsu-font: system-ui, -apple-system, \"Segoe UI\", sans-serif;\n  display: block; width: 100%; height: auto;\n  user-select: none; -webkit-user-select: none; -webkit-touch-callout: none; touch-action: none; -webkit-tap-highlight-color: transparent;\n  overflow: visible;\n}\n@media (prefers-color-scheme: dark) {\n  :root:not([data-theme=\"light\"]) .tsunagi[data-board=\"paper\"] { --tsu-paper: #262a27; --tsu-paper-deep: #262a27; --tsu-frame: #6b5632; --tsu-grid: #3f443f; --tsu-ink: #ece8dc; --tsu-coordinate: #e8d3b6; }\n}\n:root[data-theme=\"dark\"] .tsunagi[data-board=\"paper\"] { --tsu-paper: #262a27; --tsu-paper-deep: #262a27; --tsu-frame: #6b5632; --tsu-grid: #3f443f; --tsu-ink: #ece8dc; --tsu-coordinate: #e8d3b6; }\n.tsunagi * { user-select: none; -webkit-user-select: none; }\n.tsunagi .tsu-frame { fill: var(--tsu-frame); }\n.tsunagi .tsu-paper-stop-a { stop-color: var(--tsu-paper); }\n.tsunagi .tsu-paper-stop-b { stop-color: var(--tsu-paper-deep); }\n.tsunagi .tsu-solved { fill: var(--tsu-good); opacity: .14; pointer-events: none; }\n.tsunagi .tsu-grid { fill: none; stroke: var(--tsu-grid); stroke-width: 1px; vector-effect: non-scaling-stroke; }\n.tsunagi .tsu-hex-cell { fill: none; stroke: var(--tsu-grid); stroke-width: 1px; vector-effect: non-scaling-stroke; }\n.tsunagi .tsu-border { fill: none; stroke: var(--tsu-ink); stroke-width: 2.5px; vector-effect: non-scaling-stroke; opacity: .7; }\n.tsunagi .tsu-rim { fill: none; stroke: var(--tsu-ink); stroke-width: 2px; stroke-dasharray: 6 4; vector-effect: non-scaling-stroke; opacity: .7; }\n.tsunagi .tsu-blocked { fill: var(--tsu-ink); opacity: .55; }\n.tsunagi .tsu-wall { stroke: var(--tsu-ink); stroke-width: 14; stroke-linecap: round; }\n.tsunagi .tsu-deck { fill: var(--tsu-ink); opacity: .22; }\n.tsunagi .tsu-rail { stroke: var(--tsu-ink); stroke-width: 7; stroke-linecap: round; }\n.tsunagi .tsu-line polyline, .tsunagi .tsu-over-bridge { fill: none; stroke-width: 30; stroke-linecap: round; stroke-linejoin: round; }\n.tsunagi .tsu-num { font-family: var(--tsu-font); font-weight: 700; text-anchor: middle; font-variant-numeric: tabular-nums; pointer-events: none; }\n.tsunagi .tsu-coordinate { fill: var(--tsu-coordinate); font-family: var(--tsu-font); font-weight: 600; font-size: 24px; text-anchor: middle; pointer-events: none; }\n.tsunagi .tsu-glyph { font-family: var(--tsu-font); font-weight: 700; text-anchor: middle; pointer-events: none; }\n.tsunagi .tsu-portal-link { fill: none; stroke-width: 5; stroke-dasharray: 2 10; stroke-linecap: round; opacity: 0; pointer-events: none; transition: opacity .15s; }\n.tsunagi .tsu-portal:hover .tsu-portal-link, .tsunagi .tsu-portal[data-linked=\"true\"] .tsu-portal-link { opacity: .65; }\n.tsunagi .tsu-ghosts { opacity: .35; pointer-events: none; }\n.tsunagi .tsu-flag { fill: none; stroke-width: 8; transform-box: fill-box; transform-origin: center; animation: tsu-ping 1s cubic-bezier(0, 0, .2, 1) infinite; pointer-events: none; }\n.tsunagi .tsu-blast { fill: var(--tsu-shu); fill-opacity: .4; stroke: var(--tsu-shu); stroke-width: 8; transform-box: fill-box; transform-origin: center; animation: tsu-ping 1s cubic-bezier(0, 0, .2, 1) infinite; pointer-events: none; }\n@keyframes tsu-ping { 75%, 100% { transform: scale(1.7); opacity: 0; } }\n@media (prefers-reduced-motion: reduce) {\n  .tsunagi .tsu-flag, .tsunagi .tsu-blast { animation: none; }\n}\n\n.tsunagi-play {\n  --tsp-ink: #1f2320; --tsp-muted: #6b6f68; --tsp-rule: #ddd6c6; --tsp-surface: #fbf8f1; --tsp-accent: #b5452c; --tsp-good: #2f7a4f;\n  display: block; max-width: 100%; box-sizing: border-box; color: var(--tsp-ink); font-family: system-ui, -apple-system, \"Segoe UI\", sans-serif;\n}\n@media (prefers-color-scheme: dark) {\n  :root:not([data-theme=\"light\"]) .tsunagi-play { --tsp-ink: #ece8dc; --tsp-muted: #a09d93; --tsp-rule: #3a3d38; --tsp-surface: #1d201e; --tsp-accent: #ff8a6b; --tsp-good: #6fcf97; }\n}\n:root[data-theme=\"dark\"] .tsunagi-play { --tsp-ink: #ece8dc; --tsp-muted: #a09d93; --tsp-rule: #3a3d38; --tsp-surface: #1d201e; --tsp-accent: #ff8a6b; --tsp-good: #6fcf97; }\n.tsunagi-play *, .tsunagi-play *::before, .tsunagi-play *::after { box-sizing: border-box; }\n.tsunagi-play .tsp-box { position: relative; width: 100%; aspect-ratio: 1; overflow: hidden; touch-action: none; user-select: none; -webkit-user-select: none; -webkit-touch-callout: none; cursor: pointer; }\n.tsunagi-play .tsp-box:focus-visible { outline: 3px solid var(--tsp-accent); outline-offset: 2px; }\n.tsunagi-play .tsp-key-cursor { fill: none; stroke: var(--tsp-accent); stroke-width: 3px; vector-effect: non-scaling-stroke; pointer-events: none; visibility: hidden; }\n.tsunagi-play .tsp-box:focus-visible .tsp-key-cursor { visibility: visible; }\n.tsunagi-play .tsp-key-status { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }\n.tsunagi-play .tsp-box[data-over=\"true\"] { cursor: default; }\n.tsunagi-play .tsp-inner { position: absolute; top: 0; left: 0; width: 100%; transform-origin: 0 0; }\n.tsunagi-play .tsp-pad, .tsunagi-play .tsp-chips, .tsunagi-play .tsp-controls { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 10px; }\n.tsunagi-play .tsp-pad { justify-content: center; }\n.tsunagi-play .tsp-pad[hidden], .tsunagi-play [hidden] { display: none !important; }\n.tsunagi-play button { font: inherit; color: inherit; user-select: none; -webkit-user-select: none; touch-action: manipulation; }\n.tsunagi-play .tsp-button, .tsunagi-play .tsp-chip { border: 1px solid var(--tsp-rule); background: var(--tsp-surface); color: var(--tsp-ink); border-radius: 999px; min-height: 44px; min-width: 44px; padding: 0 14px; font-size: .85rem; font-weight: 600; display: inline-flex; align-items: center; justify-content: center; gap: 6px; cursor: pointer; }\n.tsunagi-play .tsp-button:hover:not(:disabled), .tsunagi-play .tsp-chip:hover { border-color: var(--tsp-ink); }\n.tsunagi-play .tsp-button:disabled { opacity: .32; cursor: default; }\n.tsunagi-play .tsp-chip { min-height: 32px; min-width: 0; padding: 0 10px; font-size: .75rem; white-space: nowrap; }\n.tsunagi-play .tsp-chip[aria-expanded=\"true\"] { border-color: var(--tsp-ink); box-shadow: 0 0 0 1px var(--tsp-ink); }\n.tsunagi-play .tsp-chip[data-strong=\"true\"] { background: var(--tsp-ink); color: var(--tsp-surface); }\n.tsunagi-play .tsp-chip .tsp-off { opacity: .3; }\n.tsunagi-play .tsp-says { margin: 6px 0 0; font-size: .8rem; color: var(--tsp-muted); }\n.tsunagi-play .tsp-progress { margin: 10px 0 0; font-weight: 600; font-size: .9rem; min-height: 1.4em; font-variant-numeric: tabular-nums; }\n.tsunagi-play[data-solved=\"true\"] .tsp-progress { color: var(--tsp-good); }\n.tsunagi-play .tsp-messages { margin: 4px 0 0; min-height: 1.5em; display: grid; align-content: start; gap: 2px; }\n.tsunagi-play .tsp-messages p { margin: 0; font-size: .85rem; line-height: 1.4; color: var(--tsp-muted); }\n.tsunagi-play .tsp-messages p[data-warn=\"true\"] { color: var(--tsp-accent); font-weight: 600; }\n.tsunagi-play .tsp-hint { margin: 6px 0 0; font-size: .8rem; color: var(--tsp-muted); }\n"

Examples

import { TSUNAGI_PLAY_STYLE } from "@johnmorrisdotca/tsunagi/play";
console.log(TSUNAGI_PLAY_STYLE.includes(".tsunagi-play")); // true
true

What it prints (run when this page was built)

View sourceEdit on GitHubMade from the source of @johnmorrisdotca/tsunagi 1.7.0.