Streamline mobile menus and refresh splash logo
This commit is contained in:
1 parent
a399e4929c
commit
a09856f8c9
6 files changed
+569
-483
No files matched your search
+243
-210
@@ -117,6 +117,7 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
|
||||
system font and drew a smooth glyph inside a pixel HUD */
|
||||
color: var(--cream); font: 700 14px Silkscreen, monospace; line-height: 1;
|
||||
cursor: pointer; -webkit-tap-highlight-color: transparent; }
|
||||
#qChip::after { content: ""; position: absolute; top: -4px; right: -4px; min-width: 44px; min-height: 44px; width: 100%; height: 100%; }
|
||||
#quit button:active { background: var(--red); border-color: var(--bad); color: #f2ead6; }
|
||||
#qAsk { position: fixed; inset: 0; z-index: 6; background: rgba(0,0,0,0.55);
|
||||
display: flex; align-items: center; justify-content: center; }
|
||||
@@ -165,10 +166,8 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
|
||||
#hot { position: fixed; top: 0; left: 0; width: 26vw; max-width: 150px; height: 15vh;
|
||||
max-height: 130px; z-index: 4; }
|
||||
#intro .card { font: 400 12px Silkscreen, monospace; color: var(--cream); text-align: center;
|
||||
line-height: 1.9; padding: 16px 14px 20px; width: min(370px, 100vw - 28px); /* CONTENT width (content-box) beside 14px+14px padding:
|
||||
94vw put the card 5-28px past the viewport on every phone (0.94w+28 > w below
|
||||
466px) and #intro's overflow-y:auto computes overflow-x to auto — that was the
|
||||
sliver of horizontal scroll Oliver caught on the splash */
|
||||
line-height: 1.9; width: 100%; min-width: 0; box-sizing: border-box;
|
||||
/* Padding belongs inside the full-width card, including on wider preview panes. */
|
||||
animation: drop 0.5s ease-out; }
|
||||
@keyframes drop { from { transform: translateY(-14px); opacity: 0; } }
|
||||
/* EVERY SCREEN FILLS THE VIEWPORT, and every screen is the same three bands: something pinned to
|
||||
@@ -180,9 +179,27 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
|
||||
#intro .card { display: flex; flex-direction: column; min-height: 100dvh; padding: 0 14px; }
|
||||
/* the slack band: the title sits in the middle of whatever is left over */
|
||||
.mid { flex: 1; display: flex; flex-direction: column; justify-content: center; padding: 12px 0; }
|
||||
/* the logo is generated pixel art (assets/logo.png), not stacked CSS text */
|
||||
#intro .logo { display: block; width: min(300px, 74vw); height: auto; margin: 0 auto 6px;
|
||||
image-rendering: pixelated; filter: drop-shadow(0 3px 0 rgba(0,0,0,0.45)); }
|
||||
/* the logo is generated pixel art (assets/logo-v2.png), not stacked CSS text */
|
||||
#intro .logoArt { position: relative; width: min(300px, 74vw); margin: 0 auto 6px; }
|
||||
#intro .logo { display: block; width: 100%; height: auto;
|
||||
image-rendering: pixelated; filter: saturate(.78) brightness(.84) drop-shadow(0 3px 0 rgba(0,0,0,0.45)); }
|
||||
.logoArt::after { content: ''; position: absolute; pointer-events: none; opacity: 0; }
|
||||
/* The drop follows the blood underline in logo-v2.png at every viewport size. */
|
||||
.logoArt::after { left: 62%; top: 94%; width: 1.3%; height: 5%;
|
||||
background: #861d18; border-radius: 35% 35% 55% 55%; transform-origin: top;
|
||||
box-shadow: inset 1px 0 rgba(196,69,48,.45); }
|
||||
#intro:not([hidden])[data-stage="splash"] #splash:not(.busy) .logoArt::after {
|
||||
animation: logoBloodDrop 14s 4s infinite; }
|
||||
@keyframes logoBloodDrop {
|
||||
0% { opacity: 0; transform: scale(.5, .15); }
|
||||
3% { opacity: .9; transform: scale(.8, .5); }
|
||||
10% { opacity: .9; transform: translateY(2px) scale(1, 1.1); animation-timing-function: ease-in; }
|
||||
14% { opacity: .8; transform: translateY(12px) scale(.7, 1.3); }
|
||||
17%, 100% { opacity: 0; transform: translateY(20px) scale(.6, .8); }
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
#intro:not([hidden])[data-stage="splash"] #splash:not(.busy) .logoArt::after { animation: none; }
|
||||
}
|
||||
/* the slogan carries most of the gap between the logo's blood and the first button */
|
||||
#intro .slogan { margin: 8px 0 20px; font-size: 11px; color: #a8a09e; letter-spacing: 0.5px; }
|
||||
#intro .slogan b { color: #c9564a; }
|
||||
@@ -191,13 +208,12 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
|
||||
/* ONE overlay, four panels, swapped by #intro[data-stage]. Keeping the backdrop and the card frame
|
||||
alive across every screen is the point — a screen that unmounts the whole overlay flashes the
|
||||
court through between menus. */
|
||||
#splash, #setup, #stats, #ranks, #newbest, #secret, #bosses, #stmode, #stsub, #vs, #train, #player, #promo, #card { display: none; }
|
||||
#splash, #setup, #stats, #ranks, #newbest, #secret, #bosses, #stmode, #vs, #train, #player, #promo, #card { display: none; }
|
||||
#intro[data-stage="splash"] #splash { display: flex; flex-direction: column; flex: 1; }
|
||||
/* flex COLUMN, not block: it is what lets the footer's margin-top:auto reach the bottom of a
|
||||
screen whose content does not fill it */
|
||||
#intro[data-stage="setup"] #setup,
|
||||
#intro[data-stage="mode"] #stmode,
|
||||
#intro[data-stage="modesub"] #stsub,
|
||||
#intro[data-stage="stats"] #stats,
|
||||
#intro[data-stage="ranks"] #ranks,
|
||||
#intro[data-stage="secret"] #secret,
|
||||
@@ -221,18 +237,13 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
|
||||
#intro.deal > .card > div:not(#splash) > *:nth-child(3) { animation-delay: 0.16s; }
|
||||
#intro.deal > .card > div:not(#splash) > *:nth-child(4) { animation-delay: 0.23s; }
|
||||
#intro.deal > .card > div:not(#splash) > *:nth-child(n+5) { animation-delay: 0.30s; }
|
||||
/* the poster wall deals its ROOMS, not one lump: the list is the whole screen there, so letting
|
||||
it arrive as a single band wastes the entrance. The list itself stops animating — a moving
|
||||
child inside a moving parent starts at twice the offset and reads as a stutter. */
|
||||
#intro.deal #stmode > #modeList { animation: none; }
|
||||
#intro.deal #modeList > .mgroup {
|
||||
animation: dealIn 0.28s cubic-bezier(0.34, 1.56, 0.64, 1) backwards; }
|
||||
#intro.deal #modeList > .mgroup:nth-child(1) { animation-delay: 0.09s; }
|
||||
#intro.deal #modeList > .mgroup:nth-child(2) { animation-delay: 0.17s; }
|
||||
#intro.deal #modeList > .mgroup:nth-child(3) { animation-delay: 0.25s; }
|
||||
#intro.deal #modeList > button { animation: dealIn 0.28s ease-out backwards; }
|
||||
#intro.deal #modeList > button:nth-child(2) { animation-delay: 0.07s; }
|
||||
#intro.deal #modeList > button:nth-child(3) { animation-delay: 0.14s; }
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
#intro.deal > .card > div:not(#splash) > *,
|
||||
#intro.deal #modeList > .mgroup { animation: none; } }
|
||||
#intro.deal > .card > div:not(#splash) > *, #intro.deal #modeList > button { animation: none; }
|
||||
}
|
||||
/* the overlay is HIDDEN during play, not removed: a restart puts it straight back, and the
|
||||
display:grid above would otherwise win over the hidden attribute */
|
||||
#intro[hidden] { display: none; }
|
||||
@@ -258,7 +269,7 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
|
||||
.hdr h2 { padding-left: 2px; } /* (its face is set with the other titles above) */
|
||||
/* UPRIGHT, not Bangers: the announcement face is drawn slanted, and a slanted arrow read as
|
||||
italic on every screen. Silkscreen's ASCII < > are straight pixel arrows. */
|
||||
.chev { width: 34px; height: 34px; flex: none; background: none; border: 2px solid var(--line);
|
||||
.chev { width: 44px; height: 44px; flex: none; background: none; border: 2px solid var(--line);
|
||||
color: var(--acid); font: 700 14px Silkscreen, monospace; line-height: 1; cursor: pointer;
|
||||
-webkit-tap-highlight-color: transparent; }
|
||||
/* the BODY COUNT headline row: three boxed figures sharing one frame */
|
||||
@@ -304,27 +315,27 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
|
||||
/* the secondary doors NAME (Jersey) and carry a NUMBER (Silkscreen, acid) — the text reduction
|
||||
turned "RANKINGS / WORLD BOARD" into "BOARD / BEST 612": a label and a fact, no description */
|
||||
.btn.sec { font: 400 18px/1.2 'Jersey 15', Silkscreen, monospace; letter-spacing: 1px; }
|
||||
#qNo { font: 400 17px/1.2 'Jersey 15', Silkscreen, monospace; letter-spacing: 1px; }
|
||||
#qYes { font: 400 17px/1.2 'Jersey 15', Silkscreen, monospace; letter-spacing: 1px; }
|
||||
.btn.sec i { display: block; margin-top: 3px; font: 700 10px Silkscreen, monospace;
|
||||
font-style: normal; color: var(--acid); letter-spacing: 1px; }
|
||||
/* THE PLAYER BLOCK: the live man on the left, everything that changes him on the right, so a tap
|
||||
and its result are never more than a thumb apart and the whole of "who am I" is one screenful. */
|
||||
#who, #who2 { display: flex; gap: 12px; align-items: flex-start; }
|
||||
#whoCtl, #pCtl { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 7px; }
|
||||
#who2 { display: flex; gap: 12px; align-items: flex-start; }
|
||||
#pCtl { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 7px; }
|
||||
/* The live player. `position: static` is not decoration: the bare `canvas` rule above is the COURT
|
||||
— fixed, full-bleed, 100dvh — and it catches any canvas in the document, so this one flew to the
|
||||
top-left corner of the screen until it was told otherwise. Every geometry property that rule
|
||||
sets has to be undone here. The buffer is the sprite's own 96x224, so width follows height. */
|
||||
#look, #pLook { position: static; display: block; flex: none; margin: 0;
|
||||
#pLook { position: static; display: block; flex: none; margin: 0;
|
||||
width: auto; height: 176px; touch-action: auto; image-rendering: pixelated; }
|
||||
/* the tour is now TWO WORDS, not two portraits — the live player next to them is the picture, and
|
||||
a second drawing of the same man (front-facing, and deaf to every colour on this screen) was
|
||||
both a duplicate and the reason the court list started below the fold */
|
||||
#tours, #pTours { display: flex; gap: 7px; }
|
||||
#tours button, #pTours button { flex: 1; padding: 10px 2px; background: var(--panel); border: 2px solid var(--line);
|
||||
#pTours { display: flex; gap: 7px; }
|
||||
#pTours button { flex: 1; padding: 10px 2px; background: var(--panel); border: 2px solid var(--line);
|
||||
color: var(--dim); font: 400 10px Silkscreen, monospace; letter-spacing: 1px;
|
||||
cursor: pointer; -webkit-tap-highlight-color: transparent; }
|
||||
#tours button.on, #pTours button.on { border-color: var(--acid); background: rgba(188,212,85,0.09); color: var(--cream); }
|
||||
#pTours button.on { border-color: var(--acid); background: rgba(188,212,85,0.09); color: var(--cream); }
|
||||
/* THE IDENTITY SCREEN (the leagues, phase 1): name row = the input plus the die. */
|
||||
#nameRow { display: flex; gap: 8px; }
|
||||
#nameRow .tin { flex: 1; margin: 0; }
|
||||
@@ -538,110 +549,57 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
|
||||
text-decoration: none; letter-spacing: 0.5px; }
|
||||
/* a row, not a stacked tile: three 3:2 tiles stacked fill a phone twice over, and the art still
|
||||
reads at this size. -webkit-tap-highlight-color kills iOS's blue flash over the pixel art. */
|
||||
/* MODE posters are pure BUTTONS, not selectors (Oliver: the remembered highlight made each one
|
||||
"a checkbox and a button combined"). Every card is full-bright and equal because every card is
|
||||
a live answer to the header's question; tapping answers and advances. The only memory shown is
|
||||
the LAST tag — an indicator, not a selection. A border that means "SERVE UP will use this"
|
||||
exists solely on the final screen, where a confirm button actually follows. */
|
||||
#modeList button.mode, #modeSub button.mode { display: flex; align-items: flex-end; width: 100%; min-height: 96px;
|
||||
margin: 0 0 8px; padding: 7px 9px; background-size: cover; background-position: center bottom;
|
||||
image-rendering: pixelated; border: 2px solid var(--line); color: inherit; font: inherit;
|
||||
text-align: left; cursor: pointer; -webkit-tap-highlight-color: transparent;
|
||||
position: relative; }
|
||||
/* THE CAPTION'S VEIL (2026-08-31). The blur went in and came straight back out — on
|
||||
nearest-neighbour art it read as a smear, not as depth (Oliver: "sieht komisch aus"). What is
|
||||
left is what he asked for instead: a gradient RISING FROM THE BOTTOM EDGE, solid where the
|
||||
words are and gone by the time it reaches the art. Same job, no filter, no edge. */
|
||||
#modeList button.mode::after, #modeSub button.mode::after {
|
||||
content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 62%;
|
||||
background: linear-gradient(0deg, rgba(6,10,8,0.94) 0%, rgba(6,10,8,0.86) 22%,
|
||||
rgba(6,10,8,0.5) 58%, rgba(6,10,8,0) 100%);
|
||||
pointer-events: none; }
|
||||
/* the words and the chips ride ABOVE the veil — a pseudo-element is the last thing painted */
|
||||
#modeList .cb, #modeSub .cb { position: relative; z-index: 2; }
|
||||
#modeList b, #modeSub b { text-shadow: 0 1px 0 #000, 0 0 6px rgba(0,0,0,0.9); }
|
||||
/* the subtitle carries the mode's one-line rules over busy art, so it gets WHITE at full
|
||||
opacity, a size up from the court rows' 9px, and a solid black underlay — "barely readable"
|
||||
at cream-on-crowd was the report from the first live look */
|
||||
#modeList i, #modeSub i { color: #fff; font-size: 10px; margin-bottom: 0;
|
||||
text-shadow: 0 1px 0 #000, 1px 0 0 #000, -1px 0 0 #000, 0 -1px 0 #000, 0 0 7px #000; }
|
||||
/* THE SECTIONS (THE SEASON): the wall is three rooms — one path up, a room to practise in, a
|
||||
room where nothing counts — so each is a real section with its posters INSIDE it, not a label
|
||||
floating over a stack. The heading is deliberately NOT the announcement face (Oliver: it read
|
||||
as a second card headline and the two competed); Silkscreen, big and letter-spaced, keeps it
|
||||
a label of a room. No rule, no left band either — the air above each heading does the
|
||||
grouping, which is what air is for. */
|
||||
#modeList .mgroup { margin: 0 0 10px; }
|
||||
#modeList .mgroup + .mgroup { margin-top: 17px; }
|
||||
/* HOW A ROOM DEALS ITS POSTERS (the text reduction). The wall used to be six identical 148px
|
||||
slabs and ran over two screens; the fixture is a HERO now, the training pair stands side by
|
||||
side, and the casual three stack shorter. All of it fits one screen. */
|
||||
#modeList .mgroup .shelf { display: flex; flex-direction: column; }
|
||||
#modeList .mgroup.pair .shelf { flex-direction: row; gap: 8px; }
|
||||
#modeList .mgroup.pair button.mode { flex: 1; min-width: 0; min-height: 106px; margin-bottom: 0; }
|
||||
#modeList .mgroup.stack button.mode { min-height: 96px; }
|
||||
#modeList .mgroup.hero button.mode { min-height: 142px; }
|
||||
/* the poster names: Jersey NAMES, and the hero is the one poster that CALLS (Bangers) */
|
||||
#modeList .cb b { font: 400 30px/1 'Jersey 15', Bangers, cursive; letter-spacing: 0.5px; }
|
||||
#modeList .mgroup.pair .cb b { font-size: 27px; }
|
||||
#modeList .mgroup.hero .cb b { font: 400 32px/0.95 Bangers, cursive; letter-spacing: 1px; }
|
||||
/* ...and the hero's second line is the fixture, the only strap left on the wall */
|
||||
#modeList .mgroup.hero .cb i { color: #f0cfc4; font-size: 10px; margin-top: 3px; }
|
||||
/* text-align rides down from the card, which centres everything on these screens — the section
|
||||
header is a header, and headers on this screen are left-aligned (the .hdr title, the posters'
|
||||
own captions) */
|
||||
#modeList .mgroup > header { margin: 0 0 11px; text-align: left; }
|
||||
/* THE SHELF LABELS SHOUT, quietly (2026-08-31, Oliver tried three: Bangers first, then the HUD
|
||||
face, then the platform's UI face, and came back to the announcement face). It works here now
|
||||
because the header lost its rule and its band and gained air: the size does the hierarchy
|
||||
(20 against the screen title's 26) instead of the typeface having to. */
|
||||
/* SCOPED TO THE HEADER, not to the section: the posters live INSIDE .mgroup now, so a bare
|
||||
descendant selector here restyled every caption on the wall (it stayed invisible while this
|
||||
rule still named Silkscreen — the moment the family changed, the posters changed with it). */
|
||||
#modeList .mgroup > header b { display: block; letter-spacing: 0.5px; color: var(--acid);
|
||||
text-shadow: none; font: 400 17px/1.2 'Jersey 15', Bangers, cursive;
|
||||
letter-spacing: 2px; }
|
||||
/* ...and the strap goes back to the quiet register under it: a shouted line over a system-sans
|
||||
line was two foreign voices in one header */
|
||||
#modeList .mgroup > header i { display: block; margin: 4px 0 0; letter-spacing: 0.5px;
|
||||
color: var(--dim); font-style: normal; text-shadow: none;
|
||||
font: 400 9px/1.4 Silkscreen, monospace; }
|
||||
#modeList .mgroup button.mode:last-child { margin-bottom: 0; }
|
||||
/* THE SECOND STEP (config.MENU) has its own SCREEN in the wizard: sub-mode cards are
|
||||
full-height posters centred on their scene, and the difficulty chips are plain acid-outline
|
||||
buttons stacked tall — art for EASY/MEDIUM/HARD would be forced, and the chip reads faster. */
|
||||
/* alone on their own screens, the posters get room to be posters — and the first card clears
|
||||
the header by the same breath the h3 rows used to give the old single page */
|
||||
#stmode #modeList, #stsub #modeSub { margin-top: 12px; }
|
||||
#stmode button.mode, #modeSub button.mode { min-height: 148px; }
|
||||
#modeSub { display: flex; flex-wrap: wrap; gap: 8px; }
|
||||
#modeSub button.mode { background-position: center; }
|
||||
#modeSub button.chip { flex: 1 1 100%; display: flex; align-items: center; justify-content: space-between;
|
||||
gap: 10px; padding: 26px 16px; background: var(--panel); border: 2px solid var(--line);
|
||||
color: var(--cream); cursor: pointer; -webkit-tap-highlight-color: transparent;
|
||||
position: relative; text-align: left; }
|
||||
#modeSub button.chip b { font: 400 30px/1 'Jersey 15', Silkscreen, monospace; letter-spacing: 1px; }
|
||||
/* how hard it is, counted: as many pips lit as the chip is steep, and the last one wears the
|
||||
colour the game uses for a threat */
|
||||
.pips { display: flex; gap: 5px; }
|
||||
.pips i { width: 10px; height: 10px; background: rgba(255,255,255,0.12); }
|
||||
.pips i.on { background: var(--acid); }
|
||||
#modeSub button.chip.hot .pips i.on { background: var(--bad); }
|
||||
#courtList button.court { display: flex; align-items: center; gap: 11px; width: 100%;
|
||||
/* The additional modes fit in two rows; the friend has a door of its own on the splash. */
|
||||
#modeList { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin: 18px 0; }
|
||||
#modeList button.mode { display: flex; align-items: flex-end; position: relative; min-width: 0;
|
||||
min-height: 150px; padding: 12px; border: 2px solid var(--line); color: var(--cream);
|
||||
background-size: cover; background-position: center; image-rendering: pixelated;
|
||||
text-align: left; cursor: pointer; }
|
||||
#modeList button.mode:last-child { grid-column: 1 / -1; }
|
||||
#modeList button.mode::after { content: ''; position: absolute; inset: 0; pointer-events: none;
|
||||
background: linear-gradient(0deg, rgba(6,10,8,.94), rgba(6,10,8,.2) 65%, transparent); }
|
||||
#modeList .cb { position: relative; z-index: 1; }
|
||||
#modeList .cb b { font: 400 29px/1 'Jersey 15', cursive; letter-spacing: .5px; }
|
||||
.playerLink { display: flex; align-items: center; justify-content: space-between; gap: 12px;
|
||||
width: 100%; min-height: 60px; margin: 16px 0 0; padding: 10px 12px; text-align: left;
|
||||
background: var(--panel); border: 2px solid var(--line); color: var(--cream); cursor: pointer; }
|
||||
.playerLink b { display: block; font: 22px/1 'Jersey 15', cursive; }
|
||||
.playerLink i { display: block; margin-top: 5px; font: 9px Silkscreen, monospace;
|
||||
color: var(--dim); font-style: normal; }
|
||||
.playerLink u { font: 17px 'Jersey 15', cursive; text-decoration: none; color: var(--acid); }
|
||||
.opponentRow { display: flex; align-items: center; gap: 14px; margin-top: 18px; text-align: left; }
|
||||
.opponentRow img { height: 50px; width: 48px; object-fit: contain; image-rendering: pixelated; }
|
||||
.opponentRow b { display: block; font: 24px 'Jersey 15', cursive; }
|
||||
.opponentRow i { font: 10px Silkscreen, monospace; font-style: normal; color: var(--dim); }
|
||||
.setupNote { margin: 18px 0 0; font: 10px/1.6 Silkscreen, monospace; color: var(--dim); }
|
||||
#sDifficulty[hidden], #sOpponent[hidden], #sClimb[hidden], #lockedCourts[hidden] { display: none; }
|
||||
#difficultyList { margin: 0; }
|
||||
#difficultyList button { font: 20px 'Jersey 15', cursive; }
|
||||
.disclosure { margin: 14px 0; text-align: left; border-top: 1px solid var(--line); }
|
||||
.disclosure > summary { display: flex; align-items: center; justify-content: space-between;
|
||||
min-height: 48px; gap: 10px; list-style: none; cursor: pointer; color: var(--cream);
|
||||
font: 18px/1.2 'Jersey 15', cursive; letter-spacing: .5px; }
|
||||
.disclosure > summary::-webkit-details-marker { display: none; }
|
||||
.disclosure > summary::after { content: '+'; color: var(--acid); font: 16px Silkscreen, monospace; }
|
||||
.disclosure[open] > summary::after { content: '−'; }
|
||||
#cdStats { display: flex; justify-content: space-between; align-items: center; margin: 16px 0 0;
|
||||
min-height: 52px; border-color: #627340; color: var(--acid); text-align: left; font-size: 23px; }
|
||||
#pNote[hidden] { display: none; }
|
||||
:is(#courtList, #lockedCourtList) button.court { display: flex; align-items: center; gap: 11px; width: 100%;
|
||||
margin: 0 0 8px; padding: 7px; background: var(--panel);
|
||||
border: 2px solid var(--line); color: inherit; font: inherit; text-align: left;
|
||||
cursor: pointer; -webkit-tap-highlight-color: transparent; }
|
||||
#courtList button.court.on { border-color: var(--acid); background: rgba(188,212,85,0.09); }
|
||||
#courtList img { width: 84px; height: 60px; display: block; image-rendering: pixelated; flex: none;
|
||||
:is(#courtList, #lockedCourtList) button.court.on { border-color: var(--acid); background: rgba(188,212,85,0.09); }
|
||||
:is(#courtList, #lockedCourtList) img { width: 84px; height: 60px; display: block; image-rendering: pixelated; flex: none;
|
||||
object-fit: cover; }
|
||||
#courtList .cb, #modeList .cb, #modeSub .cb { flex: 1; min-width: 0; }
|
||||
#courtList b, #modeList b, #modeSub b { display: block; font: 700 15px Silkscreen, monospace; color: var(--cream); letter-spacing: 0.5px; }
|
||||
:is(#courtList, #lockedCourtList) .cb, #modeList .cb { flex: 1; min-width: 0; }
|
||||
:is(#courtList, #lockedCourtList) b, #modeList b { display: block; font: 700 15px Silkscreen, monospace; color: var(--cream); letter-spacing: 0.5px; }
|
||||
/* the court NAMES itself (Jersey) and its two meters say the rest — the description line is gone */
|
||||
#courtList b { font: 400 22px/1.1 'Jersey 15', Silkscreen, monospace; letter-spacing: 0.5px; }
|
||||
#courtList i { margin: 3px 0 5px; font: 700 9px Silkscreen, monospace; color: var(--bad);
|
||||
:is(#courtList, #lockedCourtList) b { font: 400 22px/1.1 'Jersey 15', Silkscreen, monospace; letter-spacing: 0.5px; }
|
||||
:is(#courtList, #lockedCourtList) i { margin: 3px 0 5px; font: 700 9px Silkscreen, monospace; color: var(--bad);
|
||||
letter-spacing: 1px; }
|
||||
#courtList i[hidden] { display: none; }
|
||||
#courtList i, #modeList i, #modeSub i { display: block; margin: 4px 0 6px; font: 400 10px Silkscreen, monospace; color: #c9c4b8; font-style: normal; }
|
||||
:is(#courtList, #lockedCourtList) i[hidden] { display: none; }
|
||||
:is(#courtList, #lockedCourtList) i, #modeList i { display: block; margin: 4px 0 6px; font: 400 10px Silkscreen, monospace; color: #c9c4b8; font-style: normal; }
|
||||
/* The two meters read straight off the court's own bounce numbers (see main.js), so they cannot
|
||||
drift away from what the ball actually does on it. */
|
||||
.meter { display: flex; align-items: center; gap: 6px; margin-top: 4px;
|
||||
@@ -661,21 +619,105 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
|
||||
z-index: 5; color: #ffd8ca; background: #59251f; padding: 4px 6px;
|
||||
font: 10px Silkscreen, monospace; pointer-events: none; }
|
||||
#recLamp[hidden], body:has(#intro:not([hidden])) #recLamp { display: none; }
|
||||
#recorder { border-top: 1px solid var(--line); padding-top: 10px; margin-top: 14px; }
|
||||
#recorder { margin-top: 24px; margin-bottom: 24px; }
|
||||
#recorder p { font: 11px/1.5 Silkscreen, monospace; color: var(--dim); }
|
||||
#recorder .btn { min-height: 44px; }
|
||||
#recorder .btn:disabled { opacity: 0.4; }
|
||||
#recNew[hidden] { display: none; }
|
||||
/* Gore gets a front door; the records become quiet navigation, with full touch targets. */
|
||||
#mGore { display: flex; align-items: center; justify-content: center; gap: 12px;
|
||||
padding: 9px 12px; border-color: #804338; background: #281b19; color: #f1d6bf; }
|
||||
#mGore b { font: 400 23px/1.1 Bangers, cursive; letter-spacing: 1px; }
|
||||
#mGore i { margin: 0; color: #d5aa91; font: 400 9px/1.5 Silkscreen, monospace; letter-spacing: 0; }
|
||||
/* Equal-sized entrances: solid mode colour fades into each mode's original poster. */
|
||||
#mStart, #mGore { display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||||
box-sizing: border-box; height: 76px; min-height: 76px; padding: 10px 12px; gap: 4px;
|
||||
background-size: cover; background-position: center; image-rendering: pixelated;
|
||||
color: #f6e7dc; text-align: center; }
|
||||
#mStart { background-image: linear-gradient(90deg, #a4231f 0%, #a4231f 18%,
|
||||
rgba(164,35,31,.80) 30%, rgba(164,35,31,.12) 50%, rgba(0,0,0,.22) 100%), url('assets/mode_cup.png'); }
|
||||
#mGore { border-color: #804338; background-color: #281b19;
|
||||
background-image: linear-gradient(90deg, #281b19 0%, #281b19 18%,
|
||||
rgba(40,27,25,.80) 30%, rgba(40,27,25,.12) 50%, rgba(0,0,0,.22) 100%), url('assets/mode_gore.png'); }
|
||||
#mStart b, #mGore b { font: 400 26px/1.1 Bangers, cursive; letter-spacing: 1px;
|
||||
text-shadow: 0 1px 3px rgba(0,0,0,.85); }
|
||||
#mStart i, #mGore i { margin: 0; color: #f0d8c8; font: 400 9px/1.5 Silkscreen, monospace;
|
||||
letter-spacing: .3px; text-shadow: 0 1px 3px #000; }
|
||||
.splashLinks { display: flex; gap: 4px; margin-top: 5px; }
|
||||
.splashLinks .btn.sec { flex: 1; width: auto; min-height: 44px; padding: 7px 2px;
|
||||
background: transparent; border-color: transparent; color: #ccc4b5;
|
||||
font-size: 16px; letter-spacing: 0; }
|
||||
.splashLinks #mOther { flex: 1.5; }
|
||||
.splashDoors { --door-height: 58px; display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 4px; }
|
||||
.splashDoors .btn.sec { display: flex; align-items: center; justify-content: center;
|
||||
min-height: var(--door-height); padding: 10px; text-align: center; font-size: 22px; letter-spacing: .3px; }
|
||||
#mOther { border-color: #8eaa4d; color: #e6efbf; background-color: #263421;
|
||||
background-image: linear-gradient(90deg, #263421 0%, #263421 18%,
|
||||
rgba(38,52,33,.80) 30%, rgba(38,52,33,.20) 50%, rgba(0,0,0,.38) 100%), url('assets/mode_arcade.png'); }
|
||||
#mVs { border-color: #607e6b; color: #edf1e8; background-color: #26322c;
|
||||
background-image: linear-gradient(90deg, #26322c 0%, #26322c 18%,
|
||||
rgba(38,50,44,.80) 30%, rgba(38,50,44,.20) 50%, rgba(0,0,0,.38) 100%), url('assets/mode_vs.png'); }
|
||||
#mOther, #mVs { background-size: cover; background-position: center; image-rendering: pixelated;
|
||||
text-shadow: 0 1px 2px #000, 0 0 5px #000; }
|
||||
/* One occasional drop: Gore's lower edge -> friend button -> a slow run down its face.
|
||||
50px bridges the 44px court link, 4px row gap and the friend's 2px border.
|
||||
Both pieces share one CSS clock; no game-loop work, DOM additions or touch interception. */
|
||||
#mVs { position: relative; --blood-gap: 50px; }
|
||||
#mVs::before, #mVs::after { content: ''; position: absolute; pointer-events: none; opacity: 0; }
|
||||
#mVs::before { z-index: 2; right: 14px; top: calc(-1 * var(--blood-gap)); width: 6px; height: 9px;
|
||||
background: linear-gradient(90deg, #690b12 0%, #bb2027 55%, #e04436 75%, #8e1019 100%);
|
||||
clip-path: polygon(40% 0, 60% 0, 100% 55%, 100% 78%, 70% 100%, 30% 100%, 0 78%, 0 55%); }
|
||||
#mVs::after { z-index: 1; right: 16px; top: 0; width: 2px; height: calc(100% - 3px);
|
||||
background: #9a1820; box-shadow: 1px 0 #510b10; transform-origin: top; }
|
||||
#intro[data-stage="splash"] #splash:not(.busy) #mVs::before { animation: menuBloodDrop 18s 3s infinite; }
|
||||
#intro[data-stage="splash"] #splash:not(.busy) #mVs::after { animation: menuBloodTrail 18s 3s infinite; }
|
||||
@keyframes menuBloodDrop {
|
||||
0% { opacity: 0; transform: translateY(0) scale(.25, .4); }
|
||||
2% { opacity: 1; transform: translateY(1px) scale(.9, 1.1); }
|
||||
5% { opacity: 1; transform: translateY(4px) scale(1, 1.3); animation-timing-function: ease-in; }
|
||||
7% { opacity: 1; transform: translateY(var(--blood-gap)) scale(.8, 1.2); }
|
||||
7.5% { opacity: 1; transform: translateY(var(--blood-gap)) scale(1.7, .55); }
|
||||
9% { opacity: 1; transform: translateY(calc(var(--blood-gap) + 2px)) scale(1); animation-timing-function: linear; }
|
||||
32% { opacity: 1; transform: translateY(calc(var(--blood-gap) + var(--door-height) - 10px)) scale(1, 1.15); }
|
||||
35% { opacity: .8; transform: translateY(calc(var(--blood-gap) + var(--door-height) + 8px)) scale(.7, 1.3); }
|
||||
36%, 100% { opacity: 0; transform: translateY(calc(var(--blood-gap) + var(--door-height) + 16px)); }
|
||||
}
|
||||
@keyframes menuBloodTrail {
|
||||
0%, 7% { opacity: 0; transform: scaleY(0); }
|
||||
7.5% { opacity: .85; transform: scaleY(.04); }
|
||||
9% { opacity: .85; transform: scaleY(.06); animation-timing-function: linear; }
|
||||
32% { opacity: .85; transform: scaleY(1); }
|
||||
35% { opacity: .6; transform: scaleY(1); }
|
||||
48%, 100% { opacity: 0; transform: scaleY(1); }
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
#intro[data-stage="splash"] #splash:not(.busy) #mVs::before,
|
||||
#intro[data-stage="splash"] #splash:not(.busy) #mVs::after { animation: none; }
|
||||
}
|
||||
.courtLink { display: block; width: 100%; min-height: 44px; padding: 6px 0; border: 0;
|
||||
background: transparent; color: #d5aa91; font: 10px Silkscreen, monospace; cursor: pointer; }
|
||||
.splashLinks { margin-top: 0; }
|
||||
#splash #build { margin-top: 3px; }
|
||||
@media (max-height: 680px) {
|
||||
#intro .logoArt { width: min(220px, 55vw); }
|
||||
#intro .slogan { font-size: 9px; margin: 4px 0 8px; }
|
||||
#splash .mid { padding: 6px 0; }
|
||||
#splash #plaque { margin-top: 8px; }
|
||||
.splashDoors { --door-height: 50px; }
|
||||
.splashDoors .btn.sec { font-size: 20px; }
|
||||
#splash .foot { padding-top: 4px; }
|
||||
#modeList button.mode { min-height: 128px; }
|
||||
}
|
||||
:is(#courtList, #lockedCourtList) button.court.lock { opacity: .7; }
|
||||
#lockedCourtList button.court img { width: 56px; height: 40px; }
|
||||
#lockedCourtList .meter { display: none; }
|
||||
.tabs button { min-height: 44px; }
|
||||
#pTours button { min-height: 44px; }
|
||||
/* Colour choices span the screen: six swatches still have 44px targets on a 320px phone. */
|
||||
#who2 { align-items: center; }
|
||||
#pLook { height: 144px; }
|
||||
#pTours { flex-direction: column; }
|
||||
.playerColours { display: flex; flex-direction: column; gap: 7px; margin: 12px 0 20px; }
|
||||
.playerColours .swrow { gap: 5px; }
|
||||
.playerColours .swrow button { min-height: 44px; aspect-ratio: auto; height: 44px; }
|
||||
.playerColours .swlab:not(:first-child) { margin-top: 8px; }
|
||||
#pName { min-width: 0; }
|
||||
#intro h3 span { font-size: 9px; }
|
||||
button:focus-visible, summary:focus-visible { outline: 2px solid var(--acid); outline-offset: 3px; }
|
||||
/* the splash's bottom band: no bar, no rule — the court art has to keep running under it */
|
||||
.foot.bare { border: 0; background: none; }
|
||||
.foot.bare .btn.big { margin-bottom: 10px; }
|
||||
@@ -696,6 +738,8 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
|
||||
#intro.deal #splash #plaque { animation-delay: 0.02s; }
|
||||
#intro.deal #splash #mStart { animation-delay: 0.12s; }
|
||||
#intro.deal #splash #mGore { animation-delay: 0.19s; }
|
||||
#intro.deal #splash #mCourt { animation-delay: 0.23s; }
|
||||
#intro.deal #splash .foot.bare > .splashDoors { animation-delay: 0.26s; }
|
||||
/* the navigation row is a CLASS, so it needs the same weight as the shorthand above
|
||||
to override its delay — the id selectors around it get there on their own */
|
||||
#intro.deal #splash .foot.bare > .splashLinks { animation-delay: 0.26s; }
|
||||
@@ -711,7 +755,7 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
|
||||
#boot u { display: block; margin-top: 7px; text-align: center; text-decoration: none;
|
||||
font: 400 9px Silkscreen, monospace; color: var(--dim); letter-spacing: 2px; }
|
||||
#splash.busy #boot { display: block; }
|
||||
#splash.busy .btn, #splash.busy .splashLinks, #splash.busy #build { display: none; }
|
||||
#splash.busy .btn, #splash.busy .splashLinks, #splash.busy .splashDoors, #splash.busy .courtLink, #splash.busy #build { display: none; }
|
||||
/* NOTHING DATA-DRIVEN DURING THE BOOT (2026-08-31, Oliver: "die oberfläche ist hier regelmäßig
|
||||
in einem undefinierten zustand"). The plaque used to stand there as a skeleton, which is still
|
||||
a plaque saying LEVEL 1 to a level-25 player for as long as the profile takes to land. The
|
||||
@@ -744,6 +788,7 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
|
||||
font-style: normal; color: var(--dim); letter-spacing: 1px; }
|
||||
/* a GRID, not a wrapping flex row: `flex: 1 1 62px` gave the last row's tiles the whole width
|
||||
to share, so a shelf of nine came out in three different sizes (Oliver) */
|
||||
#stW > .none { grid-column: 1 / -1; }
|
||||
#stW { display: grid; grid-template-columns: repeat(auto-fill, minmax(66px, 1fr)); gap: 6px; }
|
||||
.wtile { padding: 8px 2px; border: 2px solid var(--line); background: var(--panel); }
|
||||
.wtile img { width: 34px; height: 34px; object-fit: contain; display: block; margin: 0 auto 2px;
|
||||
@@ -818,12 +863,12 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
|
||||
<canvas id="c" width="480" height="270"></canvas>
|
||||
<div id="safe"></div>
|
||||
<div id="hint">move: WASD/arrows · serve/swing: SPACE · aim: hold a direction while swinging · T: tuning panel</div>
|
||||
<div id="quit" hidden><button id="qChip">X</button></div>
|
||||
<div id="quit" hidden><button id="qChip" aria-label="Pause match">X</button></div>
|
||||
<div id="qAsk" hidden>
|
||||
<div class="qBox">
|
||||
<p>LEAVE THE MATCH?</p>
|
||||
<button class="btn big" id="qYes">BACK TO THE MENU</button>
|
||||
<button class="btn sec" id="qNo">PLAY ON</button>
|
||||
<button class="btn big" id="qNo">PLAY ON</button>
|
||||
<button class="btn sec" id="qYes">LEAVE MATCH</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="intro" data-stage="splash">
|
||||
@@ -840,7 +885,7 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
|
||||
<div id="splash" class="busy">
|
||||
<div id="hot"></div>
|
||||
<div class="mid">
|
||||
<img class="logo" src="assets/logo.png" alt="Splatter Tennis">
|
||||
<div class="logoArt"><img class="logo" src="assets/logo-v2.png" alt="Splatter Tennis"></div>
|
||||
<p class="slogan">Fifteen. Thirty. Forty. <b>Dead.</b></p>
|
||||
<!-- THE PLAYER PLAQUE (the leagues): centred under the title on a translucent surface,
|
||||
so the foot's blood pool stays visible. One tap opens the card (refreshSplash fills). -->
|
||||
@@ -864,66 +909,41 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
|
||||
already knows the answer to. It names the next cup fixture now and enters the draw;
|
||||
Gore has its own secondary door; the other modes live behind OTHER GAMES. -->
|
||||
<button class="btn big" id="mStart"><b id="msTitle">THE CUP</b><i id="msLine"></i></button>
|
||||
<button class="btn sec" id="mGore"><b>SPLATTER GORE</b><i>NO CUP.<br>JUST CARNAGE.</i></button>
|
||||
<div class="splashLinks">
|
||||
<button class="btn sec" id="mGore"><b>SPLATTER GORE</b><i>NO CUP. JUST CARNAGE.</i></button>
|
||||
<button class="courtLink" id="mCourt" aria-label="Choose court for Splatter Gore"></button>
|
||||
<div class="splashDoors">
|
||||
<button class="btn sec" id="mOther">OTHER GAMES</button>
|
||||
<button class="btn sec" id="mStats">STATS</button>
|
||||
<button class="btn sec" id="mRanks">BOARD</button>
|
||||
<button class="btn sec" id="mVs">VS A FRIEND</button>
|
||||
</div>
|
||||
<div class="splashLinks">
|
||||
<button class="btn sec" id="mRanks">WORLD BOARD</button>
|
||||
</div>
|
||||
<p id="build"></p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- tour tiles and court rows are both built from tables in main.js (config.TOURS / config.COURTS):
|
||||
adding either is a table row, never markup -->
|
||||
<!-- THE SETUP IS A WIZARD now (Oliver: one screen of posters + chips + tiles + meters was
|
||||
"really overwhelming"). Three stages, tap advances: the mode posters, then the mode's own
|
||||
second step when it has one (classic: difficulty, zombie tennis: arcade or the cup), then
|
||||
who-and-where with SERVE UP. Every step comes up pre-selected from last time, so a
|
||||
returning player is three taps from serving. -->
|
||||
<!-- Additional solo modes; multiplayer has a separate splash entrance. -->
|
||||
<div id="stmode">
|
||||
<div class="hdr"><button class="chev" id="mBack"><</button><h2>PICK A GAME</h2></div>
|
||||
<div class="hdr"><button class="chev" aria-label="Back" id="mBack"><</button><h2>OTHER GAMES</h2></div>
|
||||
<div id="modeList"></div>
|
||||
</div>
|
||||
<div id="stsub">
|
||||
<div class="hdr"><button class="chev" id="subBack"><</button><h2 id="subTitle">DIFFICULTY</h2></div>
|
||||
<div id="modeSub"></div>
|
||||
</div>
|
||||
<div id="setup">
|
||||
<div class="hdr"><button class="chev" id="sBack"><</button><h2>MATCH SETUP</h2></div>
|
||||
<!-- ONE PLAYER BLOCK. It was four stacked sections — two big portrait tiles, then the live
|
||||
player, then a kit row, then a skin row — which said the same thing twice (the tiles were
|
||||
a second, FRONT-FACING drawing of a man who does not react to any of the colours) and ran
|
||||
the court off the bottom of the phone. The tiles are gone: the live player IS the picture,
|
||||
so who-you-are shrinks to two words, and every control that changes him stands beside him
|
||||
where you can watch it land. Kit and skin rows are built in main.js from the tables. -->
|
||||
<h3 id="s01">01 · PLAYER</h3>
|
||||
<div id="who">
|
||||
<canvas id="look" width="128" height="224"></canvas>
|
||||
<div id="whoCtl">
|
||||
<div id="tours"></div>
|
||||
<u class="swlab">KIT<i id="kitName"></i></u>
|
||||
<div id="kitRow" class="swrow"></div>
|
||||
<u class="swlab">SKIN<i id="skinName"></i></u>
|
||||
<div id="skinRow" class="swrow"></div>
|
||||
</div>
|
||||
<div class="hdr"><button class="chev" aria-label="Back" id="sBack"><</button><h2 id="sTitle">MATCH SETUP</h2></div>
|
||||
<button id="sEdit" class="playerLink">
|
||||
<span><b id="sPlayerName"></b><i id="sPlayerKit"></i></span><u>EDIT PLAYER</u>
|
||||
</button>
|
||||
<div id="sDifficulty" hidden>
|
||||
<h3>DIFFICULTY</h3>
|
||||
<div id="difficultyList" class="tabs"></div>
|
||||
</div>
|
||||
<!-- THE LEAGUE PANEL (the leagues): on the league modes this stands where the player block
|
||||
does elsewhere — the identity already lives on the card, so the setup shows the thing
|
||||
that matters HERE instead: your league, writ large, and who of its band you have
|
||||
already felled (all-time, from the stats' own per-foe tallies). Built by
|
||||
main.buildLeaguePanel, toggled by syncSetup. -->
|
||||
<div id="lgPanel">
|
||||
<div id="lgHead">
|
||||
<img id="lgBadge" alt="">
|
||||
<div><b id="lgName"></b><i id="lgSub"></i></div>
|
||||
</div>
|
||||
<div id="lgFoes"></div>
|
||||
</div>
|
||||
<h3 id="s02">02 · COURT</h3>
|
||||
<div id="sOpponent" class="opponentRow" hidden><img alt=""><span><i>OPPONENT</i><b></b></span></div>
|
||||
<p id="sClimb" class="setupNote" hidden></p>
|
||||
<h3>COURT<span>PACE · BOUNCE</span></h3>
|
||||
<div id="courtList"></div>
|
||||
<div class="foot">
|
||||
<button class="btn big" id="cStart">SERVE UP</button>
|
||||
</div>
|
||||
<details id="lockedCourts" class="disclosure" open>
|
||||
<summary id="lockedCourtLabel">LOCKED COURTS</summary>
|
||||
<div id="lockedCourtList"></div>
|
||||
</details>
|
||||
<div class="foot"><button class="btn big" id="cStart">SERVE UP</button></div>
|
||||
</div>
|
||||
<!-- THE IDENTITY (the leagues, phase 1): the first boot asks who walks on — a name (prefilled
|
||||
with a zombie name, the die rolls another), the tour and the colours. Everything here
|
||||
@@ -931,7 +951,7 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
|
||||
keys), so the two screens cannot disagree about who you are. Rebuilt on every visit by
|
||||
main.buildPlayer, so the .on states track the setup screen's. -->
|
||||
<div id="player">
|
||||
<div class="hdr"><button class="chev" id="pBack"><</button><h2>YOUR PLAYER</h2></div>
|
||||
<div class="hdr"><button class="chev" aria-label="Back" id="pBack"><</button><h2>YOUR PLAYER</h2></div>
|
||||
<h3>NAME<span>YOURS ON EVERY BOARD</span></h3>
|
||||
<div id="nameRow">
|
||||
<input id="pName" class="tin" maxlength="12" autocomplete="off" spellcheck="false">
|
||||
@@ -942,14 +962,16 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
|
||||
<canvas id="pLook" width="128" height="224"></canvas>
|
||||
<div id="pCtl">
|
||||
<div id="pTours"></div>
|
||||
<u class="swlab">KIT<i id="pKitName"></i></u>
|
||||
<div id="pKitRow" class="swrow"></div>
|
||||
<u class="swlab">SKIN<i id="pSkinName"></i></u>
|
||||
<div id="pSkinRow" class="swrow"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="playerColours">
|
||||
<u class="swlab">KIT<i id="pKitName"></i></u>
|
||||
<div id="pKitRow" class="swrow"></div>
|
||||
<u class="swlab">SKIN<i id="pSkinName"></i></u>
|
||||
<div id="pSkinRow" class="swrow"></div>
|
||||
</div>
|
||||
<div class="foot">
|
||||
<p class="cent">THE CLUB WILL LEARN YOUR NAME.</p>
|
||||
<p class="cent" id="pNote">THE CLUB WILL LEARN YOUR NAME.</p>
|
||||
<button class="btn big" id="pGo">ENTER THE CLUB</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -975,7 +997,7 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
|
||||
main.buildCard from the profile + stats; the honours row shows every badge slot, earned
|
||||
bright and unearned dark. -->
|
||||
<div id="card">
|
||||
<div class="hdr"><button class="chev" id="cdBack"><</button><h2 id="cdName">YOUR PLAYER</h2></div>
|
||||
<div class="hdr"><button class="chev" aria-label="Back" id="cdBack"><</button><h2 id="cdName">YOUR PLAYER</h2></div>
|
||||
<div id="cdTop">
|
||||
<canvas id="cdLook" width="128" height="224"></canvas>
|
||||
<div id="cdMeta">
|
||||
@@ -989,10 +1011,19 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
|
||||
</div>
|
||||
<h3>HONOURS<span>WIN THE CUP TO CLIMB</span></h3>
|
||||
<div id="cdBadges"></div>
|
||||
<h3>THE LEDGER<span>ALL TIME</span></h3>
|
||||
<h3>CAREER</h3>
|
||||
<div class="figs" id="cdFigs1"></div>
|
||||
<div style="height:8px"></div>
|
||||
<div class="figs" id="cdFigs2"></div>
|
||||
<button class="btn sec" id="cdStats">STATS<span aria-hidden="true">></span></button>
|
||||
<details class="disclosure" id="cdLeagueDetails" open>
|
||||
<summary>YOUR LEAGUE</summary>
|
||||
<div id="lgPanel">
|
||||
<div id="lgHead">
|
||||
<img id="lgBadge" alt="">
|
||||
<div><b id="lgName"></b><i id="lgSub"></i></div>
|
||||
</div>
|
||||
<div id="lgFoes"></div>
|
||||
</div>
|
||||
</details>
|
||||
<div class="foot bare">
|
||||
<button class="btn sec" id="cdEdit">EDIT PLAYER<i>NAME · TOUR · COLOURS</i></button>
|
||||
</div>
|
||||
@@ -1000,7 +1031,8 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
|
||||
<!-- THE TRAINING COURT's opponent picker: the whole ladder face-up, tap a body to drill it.
|
||||
Built in main.js from config's roster (matchFoe/LADDER), so a new rung needs no markup. -->
|
||||
<div id="train">
|
||||
<div class="hdr"><button class="chev" id="trBack"><</button><h2>TRAINING</h2></div>
|
||||
<div class="hdr"><button class="chev" aria-label="Back" id="trBack"><</button><h2>TRAINING</h2></div>
|
||||
<h3>CHOOSE AN OPPONENT</h3>
|
||||
<div id="trainList"></div>
|
||||
</div>
|
||||
<!-- VS A FRIEND. The whole lobby: your name (the world board's name — one name per player),
|
||||
@@ -1008,7 +1040,7 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
|
||||
a socket is open, so the room code and its SHARE button stand where the buttons were and
|
||||
the game stays parked behind this overlay until the room fills (main.js, THE LOBBY). -->
|
||||
<div id="vs">
|
||||
<div class="hdr"><button class="chev" id="vsBack"><</button><h2>VS A FRIEND</h2></div>
|
||||
<div class="hdr"><button class="chev" aria-label="Back" id="vsBack"><</button><h2>VS A FRIEND</h2></div>
|
||||
<div id="vsPick">
|
||||
<h3>YOU</h3>
|
||||
<input id="vsName" class="tin" maxlength="12" placeholder="YOUR NAME" autocomplete="off"
|
||||
@@ -1036,30 +1068,31 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
|
||||
</div>
|
||||
<!-- BODY COUNT. Every row in here is built by stats.js from the run records in localStorage. -->
|
||||
<div id="stats">
|
||||
<div class="hdr"><button class="chev" id="stBack"><</button><h2>BODY COUNT</h2></div>
|
||||
<div class="hdr"><button class="chev" aria-label="Back" id="stBack"><</button><h2>STATS</h2></div>
|
||||
<!-- NO TABS (2026-08-31). This screen carried three MODE tabs times three PERIOD tabs, and
|
||||
the same three boxes meant something different in every one of the nine states. It is
|
||||
your career now, in four numbers that always mean the same thing, plus the two things
|
||||
the screen is actually good at: the bodies you have collected, and the rack you fired. -->
|
||||
<div class="figs pair" id="stFigs"></div>
|
||||
<section id="recorder">
|
||||
<h3>PLAYTEST RECORDING</h3>
|
||||
|
||||
<h3>BY OPPONENT<span>ALL TIME</span></h3>
|
||||
<div id="stZ"></div>
|
||||
<h3>BY WEAPON</h3>
|
||||
<div id="stW"></div>
|
||||
<details id="recorder" class="disclosure" open>
|
||||
<summary id="recLabel">PLAYTEST RECORDING</summary>
|
||||
<p>Record league, Climb and training points to investigate difficulty. Export here after playing.</p>
|
||||
<p id="recStatus" role="status"></p>
|
||||
<button class="btn sec" id="recToggle">RECORD PLAYTEST</button>
|
||||
<button class="btn sec" id="recExport" disabled>EXPORT RECORDING</button>
|
||||
<button class="btn sec" id="recNew" hidden>REPLACE WITH NEW RECORDING</button>
|
||||
</section>
|
||||
<h3>BY OPPONENT<span>ALL TIME</span></h3>
|
||||
<div id="stZ"></div>
|
||||
<h3>BY WEAPON</h3>
|
||||
<div id="stW"></div>
|
||||
</details>
|
||||
</div>
|
||||
<!-- THREE BOARDS, one per game mode — the three do not share a currency, so they cannot share
|
||||
a ranking. Same tab strip as the stats screen's, and stats.drawRanks rewrites the column
|
||||
heading with the tab, because the number means something different on each one. -->
|
||||
<div id="ranks">
|
||||
<div class="hdr"><button class="chev" id="rkBack"><</button><h2>WORLD BOARD</h2></div>
|
||||
<div class="hdr"><button class="chev" aria-label="Back" id="rkBack"><</button><h2>WORLD BOARD</h2></div>
|
||||
<!-- TWO boards, not three (2026-08-31): classic tennis is out of competition by design now
|
||||
— no league, no record, no stakes — so ranking it contradicted the room it lives in. -->
|
||||
<div class="tabs" id="rkModes">
|
||||
@@ -1074,7 +1107,7 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
|
||||
at it, which is the whole point: it replaced a triple-tap on the in-play scoreboard that
|
||||
players kept tripping by accident. -->
|
||||
<div id="secret">
|
||||
<div class="hdr"><button class="chev" id="scBack"><</button><h2>BACK ROOM</h2></div>
|
||||
<div class="hdr"><button class="chev" aria-label="Back" id="scBack"><</button><h2>BACK ROOM</h2></div>
|
||||
<div class="foot bare">
|
||||
<button class="btn big" id="scWeapons">WEAPON TEST</button>
|
||||
<button class="btn big" id="scBosses">BOSS TEST</button>
|
||||
@@ -1086,7 +1119,7 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
|
||||
<!-- one tile per chapter boss, built from config.bossArt in main.js: adding a design to the
|
||||
roster puts it on this screen and never touches this markup -->
|
||||
<div id="bosses">
|
||||
<div class="hdr"><button class="chev" id="bsBack"><</button><h2>BOSS TEST</h2></div>
|
||||
<div class="hdr"><button class="chev" aria-label="Back" id="bsBack"><</button><h2>BOSS TEST</h2></div>
|
||||
<h3>PICK A CHAPTER<span>FULL ARSENAL</span></h3>
|
||||
<div id="bossList"></div>
|
||||
</div>
|
||||
@@ -1107,6 +1140,6 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
|
||||
</div>
|
||||
</div>
|
||||
<span id="recLamp" hidden>REC</span>
|
||||
<script type="module" src="src/main.js?v=352"></script>
|
||||
<script type="module" src="src/main.js?v=353"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in new issue
Block a user