feat: create recognizable Early-20th-Century Warship Silhouettes

This commit is contained in:
2026-08-30 01:37:50 +03:00
parent 81d70a7862
commit 5cb4d8e9f6
7 changed files with 241 additions and 12 deletions
+6 -6
View File
@@ -180,10 +180,10 @@
}
const fleetClasses = [
{ length: 4, count: 1, symbol: 'ship-4-battleship', label: 'Линкор', className: 'battleship' },
{ length: 3, count: 2, symbol: 'ship-3-cruiser', label: 'Крейсер', className: 'cruiser' },
{ length: 2, count: 3, symbol: 'ship-2-destroyer', label: 'Эсминец', className: 'destroyer' },
{ length: 1, count: 4, symbol: 'ship-1-cutter', label: 'Катер', className: 'cutter' },
{ length: 4, count: 1, symbol: 'ship-4-battleship', viewBox: '0 0 192 50', label: 'Линкор', className: 'battleship' },
{ length: 3, count: 2, symbol: 'ship-3-cruiser', viewBox: '0 0 144 40', label: 'Крейсер', className: 'cruiser' },
{ length: 2, count: 3, symbol: 'ship-2-destroyer', viewBox: '0 0 96 30', label: 'Эсминец', className: 'destroyer' },
{ length: 1, count: 4, symbol: 'ship-1-cutter', viewBox: '0 0 48 26', label: 'Катер', className: 'cutter' },
];
function sunkShipLengths(board) {
@@ -222,8 +222,8 @@
const ship = document.createElement('span');
ship.className = `fleet-ship ${shipClass.className}${sunk ? ' sunk' : ''}`;
ship.setAttribute('role', 'img'); ship.setAttribute('aria-label', `${shipClass.label}${sunk ? 'потоплен' : 'цел'}`);
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); svg.setAttribute('aria-hidden', 'true'); svg.setAttribute('focusable', 'false');
const use = document.createElementNS('http://www.w3.org/2000/svg', 'use'); use.setAttribute('href', `/ship-sprite.svg#${shipClass.symbol}`);
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); svg.setAttribute('aria-hidden', 'true'); svg.setAttribute('focusable', 'false'); svg.setAttribute('viewBox', shipClass.viewBox); svg.setAttribute('preserveAspectRatio', 'xMidYMax meet');
const use = document.createElementNS('http://www.w3.org/2000/svg', 'use'); use.setAttribute('href', `/ship-sprite.svg#${shipClass.symbol}`); use.setAttribute('width', '100%'); use.setAttribute('height', '100%');
svg.append(use); ship.append(svg); ships.append(ship);
}
row.append(ships); fleet.append(row);
+4 -4
View File
@@ -1,6 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg">
<symbol id="ship-1-cutter" viewBox="0 0 40 22"><path d="M3 15 9 6h13l7 9-5 3H8z"/><path d="M14 6V3h5v3M31 14h6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></symbol>
<symbol id="ship-2-destroyer" viewBox="0 0 72 26"><path d="M3 18 13 11h39l14 5-7 5H11z"/><path d="M23 11V5h13l5 6M45 11V7h6v4M61 16h8" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linejoin="round" stroke-linecap="round"/></symbol>
<symbol id="ship-3-cruiser" viewBox="0 0 104 32"><path d="M3 22 15 13h63l19 7-9 7H13z"/><path d="M31 13V5h19l7 8M60 13V8h10v5M76 14h11M84 10v4" fill="none" stroke="currentColor" stroke-width="2.8" stroke-linejoin="round" stroke-linecap="round"/></symbol>
<symbol id="ship-4-battleship" viewBox="0 0 136 38"><path d="M3 27 17 15h84l28 9-11 9H14z"/><path d="M30 15V5h25l8 10M68 15V8h16v7M91 16h15M100 11v5M15 22h13M111 20h14" fill="none" stroke="currentColor" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"/></symbol>
<symbol id="ship-1-cutter" viewBox="0 0 48 26"><path d="M2 19 8 13h25l10 3-5 5H7zM18 13l3-6h7l3 6zM26 7l1-5h2l1 5zM32 13l2-5h3l2 5zM5 17h7l-1-3H8z"/></symbol>
<symbol id="ship-2-destroyer" viewBox="0 0 96 30"><path d="M2 22 12 15h62l17 4-8 6H10zM20 15l4-7h14l5 7zM42 15l2-10h2l2 10zM50 15l2-9h6l3 9zM62 15l2-9h6l3 9zM6 19h9l-1-4H9zM79 19h9l-1-4h-5zM46 8l-4-6h2l5 6z"/></symbol>
<symbol id="ship-3-cruiser" viewBox="0 0 144 40"><path d="M2 29 15 19h99l25 5-10 9H13zM27 19l5-10h28l7 10zM65 19l3-14h3l3 14zM78 19l3-12h9l4 12zM96 19l3-12h9l4 12zM13 23h11l-2-5h-6zM47 22h10l-1-5h-7zM112 23h11l-2-5h-7zM71 7l-6-6h3l7 6z"/></symbol>
<symbol id="ship-4-battleship" viewBox="0 0 192 50"><path d="M2 36 18 23h135l34 7-13 11H14zM34 23l7-13h38l9 13zM84 23l4-17h5l4 17zM102 23l4-15h13l5 15zM125 23l4-15h13l5 15zM20 29h16l-2-8H24zM43 27h17l-2-8H48zM143 29h17l-2-8h-10zM163 30h18l-3-8h-11zM92 7l-8-7h4l9 7zM91 12h17v4H91z"/></symbol>
</svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 975 B

+1 -1
View File
@@ -23,7 +23,7 @@ h1, h2, h3, p { margin-top: 0; } h1 { margin-bottom: .2rem; font-size: clamp(1.7
.boards { display: grid; grid-template-columns: minmax(0, 1fr); gap: 1rem; } .board { min-width: 0; padding: .75rem; border: 1px solid #50809c; border-radius: .8rem; background: #071f32; } .board h3 { margin: 0 0 .65rem; font-size: 1rem; }
.board-grid { display: grid; grid-template-columns: 1.15rem repeat(10, minmax(0, 1fr)); gap: 2px; width: 100%; aspect-ratio: 1.1; } .axis { display: grid; place-items: center; color: #b8d3e6; font-size: clamp(.52rem, 2.2vw, .72rem); font-weight: 700; }
.cell { position: relative; min-height: 0; padding: 0; border-radius: .12rem; border: 1px solid #4e87a6; background: #167aa6; color: #fff; font-size: clamp(.68rem, 3vw, 1.15rem); line-height: 1; } .cell.ship { background: #b2c8d6; color: #1c3442; } .cell.ship::before { content: "◆"; font-size: .8em; } .cell.miss::before { content: "•"; color: #08253a; font-size: 1.15em; } .cell.hit, .cell.sunk { background: #b83e42; color: #fff; } .cell.hit::before, .cell.sunk::before { content: "×"; font-size: 1.25em; font-weight: 900; } .cell.sunk { outline: 2px solid #ffe56a; outline-offset: -3px; } .cell.target { background: #0c648b; cursor: pointer; touch-action: manipulation; } .cell.target::after { content: ""; position: absolute; inset: 27%; border: 1px dotted #b8edff; border-radius: 50%; } .cell.own-water { background: #167aa6; } .cell.unavailable { border-style: dashed; background: #0d4666; opacity: .72; } .cell.selected { outline: 3px solid #ffe56a; outline-offset: -3px; box-shadow: inset 0 0 0 2px #061827; } .cell.selected::after { content: "◎"; inset: auto; border: 0; color: #ffe56a; font-size: 1.05em; font-weight: 900; } .cell[disabled] { cursor: default; }
.fleet-status { --fleet-unit: 1.3rem; display: grid; gap: .3rem; margin-top: .7rem; padding-top: .6rem; border-top: 1px solid rgb(80 128 156 / 55%); } .fleet-row { display: grid; grid-template-columns: 4.65rem minmax(0, 1fr); align-items: center; gap: .45rem; min-height: 1.65rem; } .fleet-class { color: #b8d3e6; font-size: .72rem; font-weight: 700; } .fleet-ships { display: flex; flex-wrap: wrap; align-items: center; gap: .3rem; min-width: 0; } .fleet-ship { position: relative; display: inline-grid; place-items: center; height: 1.55rem; color: #d9edf7; } .fleet-ship svg { display: block; width: 100%; max-height: 100%; fill: currentColor; } .fleet-ship.cutter { width: var(--fleet-unit); } .fleet-ship.destroyer { width: calc(var(--fleet-unit) * 2); } .fleet-ship.cruiser { width: calc(var(--fleet-unit) * 3); } .fleet-ship.battleship { width: calc(var(--fleet-unit) * 4); height: 1.7rem; } .fleet-ship.sunk { color: #9bb0bd; opacity: .48; } .fleet-ship.sunk::before, .fleet-ship.sunk::after { position: absolute; z-index: 1; width: 112%; height: 0; border-top: .16rem solid #ff5f55; border-radius: 99rem; content: ""; opacity: 1; pointer-events: none; } .fleet-ship.sunk::before { transform: rotate(31deg); } .fleet-ship.sunk::after { transform: rotate(-31deg); }
.fleet-status { --fleet-unit: 1.45rem; display: grid; gap: .34rem; margin-top: .7rem; padding-top: .6rem; border-top: 1px solid rgb(80 128 156 / 55%); } .fleet-row { display: grid; grid-template-columns: 4.65rem minmax(0, 1fr); align-items: end; gap: .5rem; min-height: 1.8rem; } .fleet-class { align-self: center; color: #b8d3e6; font-size: .72rem; font-weight: 700; } .fleet-ships { display: flex; flex-wrap: nowrap; align-items: end; gap: .42rem; min-width: 0; } .fleet-ship { position: relative; display: inline-grid; place-items: end center; height: 1.7rem; color: #d9edf7; } .fleet-ship svg { display: block; width: 100%; max-height: 100%; fill: currentColor; } .fleet-ship.cutter { width: var(--fleet-unit); height: 1.2rem; } .fleet-ship.destroyer { width: calc(var(--fleet-unit) * 2); height: 1.4rem; } .fleet-ship.cruiser { width: calc(var(--fleet-unit) * 3); height: 1.6rem; } .fleet-ship.battleship { width: calc(var(--fleet-unit) * 4); height: 1.8rem; } .fleet-ship.sunk { color: #9bb0bd; } .fleet-ship.sunk svg { opacity: .48; } .fleet-ship.sunk::before, .fleet-ship.sunk::after { position: absolute; z-index: 1; width: 108%; height: 0; border-top: .16rem solid #ff5f55; border-radius: 99rem; content: ""; opacity: .96; pointer-events: none; } .fleet-ship.sunk::before { transform: rotate(31deg); } .fleet-ship.sunk::after { transform: rotate(-31deg); }
.shot-controls { display: flex; flex-wrap: wrap; align-items: center; gap: .7rem; margin-top: 1rem; } .shot-controls p { flex: 1 1 14rem; margin: 0; } .abort-button { margin-top: .75rem; color: #ffb7ae !important; } .reconnecting { border-color: #ffe56a; } .error-screen { border-color: #ff8e80; }
@media (min-width: 44rem) { .app-shell { display: flex; flex-direction: column; min-height: 100svh; } .app-header { align-items: center; } .connection-status { margin: 0; } #screen-connect, #screen-lobby { width: min(100%, 48rem); margin: clamp(1.25rem, 4vh, 2.5rem) auto auto; padding: clamp(1.5rem, 4vw, 2.5rem); } #screen-connect .stack-form { width: min(100%, 42rem); max-width: 42rem; } #screen-connect .button-row > * { flex-basis: 16rem; } .mode-options { grid-template-columns: repeat(2, minmax(0, 1fr)); } .boards { grid-template-columns: repeat(2, minmax(0, 1fr)); } .board-tabs { display: none; } .board[hidden] { display: block; } }
@media (min-width: 60rem) { #screen-connect { width: 100%; max-width: none; min-height: min(29rem, calc(100svh - 9rem)); display: grid; align-content: center; } #screen-connect .connection-layout { grid-template-columns: minmax(0, 1.15fr) minmax(18rem, .85fr); align-items: stretch; gap: clamp(1.5rem, 4vw, 3rem); } #screen-connect .stack-form { max-width: none; } #screen-connect .connection-support { display: flex; flex-direction: column; justify-content: center; } }