diff --git a/site/assets/css/style.css b/site/assets/css/style.css index ff16a10..2434e00 100644 --- a/site/assets/css/style.css +++ b/site/assets/css/style.css @@ -358,22 +358,74 @@ section[id] { The element itself is the face. On hover it lifts toward the viewer — up by --depth and left by --depth-x — and two pseudo-elements grow to fill the gap: ::before is the bottom wall, ::after the right wall. Each is a parallelogram - (clip-path) joining the face's edge to where the face used to be, so the - solid reads as an extruded block viewed from the right. Walls are lit in the - brand colour with a neon glow; the right one is a shade darker (light comes - from the top-left). Only transform animates (compositor-only): lift springs - (overshoot curve), release is slow, press is instant. Colours come from - --fitme-accent, so each gym glows in its own colour. */ + (clip-path) joining the face's edge to where the face used to be; they share + the diagonal from the face's corner, so the join has no gap and no overlap. + + Light model (key light top-left, the walls are the neon tubes): + - Glow lives on the walls and spills onto the page under/around them. The + halo is an outer box-shadow offset toward the walls (never painted inside + the face, no drop-shadow, so no ghost copy of the text), blooming in with + the lift and tightening on press. + - Walls: hot near-white core in the middle of their length, saturated brand + colour on the falloff, darkest at the ends; across their thickness bright + where they meet the face, darker at the far edge. The right wall is one + step darker than the bottom; both reach the darkest value (ambient + occlusion) where they meet in the corner. + - Contact shadow: a tight dark line just past the walls' far edges. + - Face: only a faint rim light near the edges, a thin specular line on the + top-left edge, slightly darker toward the bottom/right. Centre untouched. + Colours derive from --fitme-accent with color-mix(), so any gym colour works. */ :where(.site-btn, .gym-switch-btn) { position: relative; } +.site-btn, +.gym-switch-btn, +.trainer-item .trainer-pic { + --wall-hi: color-mix(in srgb, var(--fitme-accent) 40%, #fff); /* hot core of the tube */ + --wall: var(--fitme-accent); + --wall-lo: color-mix(in srgb, var(--fitme-accent) 60%, #000); + --wall-ao: color-mix(in srgb, var(--fitme-accent) 30%, #000); /* corner / far ends */ + /* bottom wall: across thickness (bright at the face, dark far edge) over along length */ + --wall-bottom: + linear-gradient(to bottom, rgba(255, 255, 255, .28) 0%, rgba(255, 255, 255, 0) 40%, rgba(0, 0, 0, .4) 100%), + linear-gradient(to right, var(--wall-lo) 0%, var(--wall) 18%, var(--wall-hi) 48%, var(--wall) 78%, var(--wall-ao) 100%); + /* right wall: one step darker, ends in the same --wall-ao at the corner */ + --wall-right: + linear-gradient(to right, rgba(255, 255, 255, .14) 0%, rgba(0, 0, 0, .12) 45%, rgba(0, 0, 0, .5) 100%), + linear-gradient(to bottom, var(--wall-lo) 0%, var(--wall) 22%, color-mix(in srgb, var(--wall-hi) 60%, var(--wall)) 50%, var(--wall-lo) 82%, var(--wall-ao) 100%); + /* halo stacks (outer only): contact shadow, tight bloom, wide ambient spill */ + --neon-on: + calc(var(--depth-x) + 1px) calc(var(--depth) + 1px) 2px rgba(0, 0, 0, .45), + calc(var(--depth-x) * .75) var(--depth) 7px 0 rgba(var(--fitme-accent-rgb), .5), + var(--depth-x) calc(var(--depth) * 1.6) 24px 2px rgba(var(--fitme-accent-rgb), .24); + --neon-dim: + calc(var(--depth-x) + 1px) calc(var(--depth) + 1px) 2px rgba(0, 0, 0, .45), + calc(var(--depth-x) * .75) var(--depth) 6px 0 rgba(var(--fitme-accent-rgb), .25), + var(--depth-x) calc(var(--depth) * 1.6) 20px 0 rgba(var(--fitme-accent-rgb), .1); + --neon-off: + calc(var(--depth-x) + 1px) calc(var(--depth) + 1px) 2px rgba(0, 0, 0, .45), + calc(var(--depth-x) * .75) var(--depth) 4px 0 rgba(var(--fitme-accent-rgb), .08), + var(--depth-x) calc(var(--depth) * 1.6) 16px 0 rgba(var(--fitme-accent-rgb), 0); + /* pressed: wall compressed to ~1px, glow pulled in close */ + --neon-press: + 1px 1px 1px rgba(0, 0, 0, .5), + 1px 1px 4px 0 rgba(var(--fitme-accent-rgb), .55), + 1px 2px 12px 0 rgba(var(--fitme-accent-rgb), .2); + /* face shading (inset): specular top-left, lit seam bottom-right, shade, faint rim */ + --face-light: + inset 1px 1px 0 color-mix(in srgb, var(--wall-hi) 55%, transparent), + inset -1px -1px 0 rgba(var(--fitme-accent-rgb), .5), + inset -3px -4px 8px -2px rgba(0, 0, 0, .55), + inset 0 0 7px -3px rgba(var(--fitme-accent-rgb), .45); +} + .site-btn, .gym-switch-btn { --depth: 5px; /* lift (and bottom wall height) */ --depth-x: 4px; /* shift to the left (and right wall width) */ --bw: 0px; /* the element's own border, so the walls start outside it */ - --wall-hi: color-mix(in srgb, var(--fitme-accent) 45%, #fff); /* hot core of the tube */ - --wall: var(--fitme-accent); - --wall-lo: color-mix(in srgb, var(--fitme-accent) 65%, #000); + --neon-face-on: var(--face-light), var(--neon-on); + --neon-face-dim: var(--face-light), var(--neon-dim); + --neon-face-off: var(--face-light), var(--neon-off); } .site-btn.sb-line, .site-btn.sb-ghost { --bw: 2px; } @@ -395,7 +447,7 @@ section[id] { top: calc(100% + var(--bw)); width: calc(100% + var(--bw) * 2 + var(--depth-x)); height: var(--depth); - background: linear-gradient(to right, var(--wall-lo) 0%, var(--wall) 1%, var(--wall-hi) 50%, var(--wall) 99%, var(--wall-lo) 100%); + background: var(--wall-bottom); clip-path: polygon(0 0, calc(100% - var(--depth-x)) 0, 100% 100%, var(--depth-x) 100%); transform: scaleY(0); } @@ -407,7 +459,7 @@ section[id] { top: calc(var(--bw) * -1); width: var(--depth-x); height: calc(100% + var(--bw) * 2 + var(--depth)); - background: linear-gradient(to bottom, var(--wall) 0%, var(--wall-lo) 100%); + background: var(--wall-right); clip-path: polygon(0 0, 100% var(--depth), 100% 100%, 0 calc(100% - var(--depth))); transform: scaleX(0); } @@ -418,7 +470,7 @@ section[id] { will-change: transform; /* the face colour fades slowly so the change to near-black never "pops" */ transition: transform 600ms cubic-bezier(.3, .7, .4, 1), box-shadow 600ms cubic-bezier(.3, .7, .4, 1), - background-color 450ms ease, border-color 450ms ease, color 450ms ease, filter 450ms ease; + background-color 450ms ease, border-color 450ms ease, color 450ms ease; } .site-btn::before, .site-btn::after, @@ -432,14 +484,10 @@ section[id] { .gym-switch-btn:hover, .gym-switch-btn:focus-visible { transform: translate(calc(var(--depth-x) * -1), calc(var(--depth) * -1)); - /* inner glow on the face (the "lit from inside" look) + a halo round the - whole solid; drop-shadow follows the clipped walls, box-shadow can't */ - box-shadow: inset 0 0 14px 1px rgba(var(--fitme-accent-rgb), .4), - inset 0 0 3px 0 rgba(var(--fitme-accent-rgb), .6), - 0 0 12px 1px rgba(var(--fitme-accent-rgb), .25); - filter: drop-shadow(0 0 5px rgba(var(--fitme-accent-rgb), .9)) drop-shadow(0 0 14px rgba(var(--fitme-accent-rgb), .55)); - transition-duration: 250ms, 250ms, 450ms, 450ms, 450ms, 450ms; - transition-timing-function: cubic-bezier(.3, .7, .4, 1.5), cubic-bezier(.3, .7, .4, 1.5), ease, ease, ease, ease; + box-shadow: var(--neon-face-on); + /* lift springs fast; the bloom eases in a little slower so it grows with the lift */ + transition-duration: 250ms, 380ms, 450ms, 450ms, 450ms; + transition-timing-function: cubic-bezier(.3, .7, .4, 1.5), ease-out, ease, ease, ease; } .site-btn:hover::before, .site-btn:focus-visible::before, @@ -451,7 +499,8 @@ section[id] { .gym-switch-btn:focus-visible::after { transform: scaleX(1); transition: transform 250ms cubic-bezier(.3, .7, .4, 1.5); } /* Neon blink: after a moment of hovering, one short stutter — like a tube - that isn't quite seated — then steady again. Opacity + filter only. */ + that isn't quite seated — then steady. Walls (opacity) and halo + (box-shadow) dim on the same keyframe times. */ .site-btn:hover, .site-btn:focus-visible, .gym-switch-btn:hover, @@ -468,6 +517,8 @@ section[id] { .site-btn:active, .gym-switch-btn:active { transform: translate(-1px, -1px); + box-shadow: var(--face-light), var(--neon-press); + animation: none; transition-duration: 34ms; } .site-btn:active::before, @@ -477,31 +528,29 @@ section[id] { } @keyframes neon-flicker-tube { - 0%, 100% { opacity: 1; } + 0%, 30%, 65%, 100% { opacity: 1; } 15% { opacity: .3; } - 30% { opacity: 1; } 50% { opacity: .55; } - 65% { opacity: 1; } } @keyframes neon-flicker-glow { - 0%, 100% { filter: drop-shadow(0 0 5px rgba(var(--fitme-accent-rgb), .9)) drop-shadow(0 0 14px rgba(var(--fitme-accent-rgb), .55)); } - 15% { filter: drop-shadow(0 0 1px rgba(var(--fitme-accent-rgb), .3)); } - 30% { filter: drop-shadow(0 0 5px rgba(var(--fitme-accent-rgb), .9)) drop-shadow(0 0 14px rgba(var(--fitme-accent-rgb), .55)); } - 50% { filter: drop-shadow(0 0 2px rgba(var(--fitme-accent-rgb), .45)); } + 0%, 30%, 65%, 100% { box-shadow: var(--neon-face-on); } + 15% { box-shadow: var(--neon-face-off); } + 50% { box-shadow: var(--neon-face-dim); } } /* ── Team photo frames: the same neon extrusion ──────────────────────────────── .trainer-pic is the face. Its ::before draws the L-shaped wall (bottom + right, - one clipped polygon, two gradients); ::after stays the existing gloss overlay - and also carries the inner glow. The frame no longer clips its children - (overflow:hidden would cut the wall), the photo fills the padding box anyway. */ + one clipped polygon, the same wall gradients); ::after stays the existing + gloss overlay and also carries the face shading. The halo is the frame's + outer box-shadow only (no inset there, the photo covers it). The frame no + longer clips its children (overflow:hidden would cut the wall). */ .trainer-item .trainer-pic { --depth: 5px; --depth-x: 4px; --bw: 4px; /* the frame's own border */ - --wall-hi: color-mix(in srgb, var(--fitme-accent) 45%, #fff); - --wall: var(--fitme-accent); - --wall-lo: color-mix(in srgb, var(--fitme-accent) 65%, #000); + --neon-face-on: var(--neon-on); + --neon-face-dim: var(--neon-dim); + --neon-face-off: var(--neon-off); overflow: visible; } @@ -515,10 +564,15 @@ section[id] { z-index: -1; pointer-events: none; opacity: 0; + /* the two gradient pairs, each sized to its wall's box (bottom strip, right strip) */ background: - linear-gradient(to right, var(--wall-lo) 0%, var(--wall) 1%, var(--wall-hi) 50%, var(--wall) 99%, var(--wall-lo) 100%) + linear-gradient(to bottom, rgba(255, 255, 255, .28) 0%, rgba(255, 255, 255, 0) 40%, rgba(0, 0, 0, .4) 100%) 0 100% / calc(100% - var(--depth-x)) var(--depth) no-repeat, - linear-gradient(to bottom, var(--wall) 0%, var(--wall-lo) 100%) + linear-gradient(to right, var(--wall-lo) 0%, var(--wall) 18%, var(--wall-hi) 48%, var(--wall) 78%, var(--wall-ao) 100%) + 0 100% / calc(100% - var(--depth-x)) var(--depth) no-repeat, + linear-gradient(to right, rgba(255, 255, 255, .14) 0%, rgba(0, 0, 0, .12) 45%, rgba(0, 0, 0, .5) 100%) + 100% 0 / var(--depth-x) 100% no-repeat, + linear-gradient(to bottom, var(--wall-lo) 0%, var(--wall) 22%, color-mix(in srgb, var(--wall-hi) 60%, var(--wall)) 50%, var(--wall-lo) 82%, var(--wall-ao) 100%) 100% 0 / var(--depth-x) 100% no-repeat; clip-path: polygon(0 calc(100% - var(--depth)), calc(100% - var(--depth-x)) calc(100% - var(--depth)), calc(100% - var(--depth-x)) 0, 100% var(--depth), 100% 100%, var(--depth-x) 100%); @@ -528,23 +582,22 @@ section[id] { .trainer-item .trainer-pic:hover, #powerRotation>div:nth-child(even) .trainer-pic:hover { transform: rotate(0deg) translate(calc(var(--depth-x) * -1), calc(var(--depth) * -1)) scale(1.03); - box-shadow: 0 0 12px 1px rgba(var(--fitme-accent-rgb), .25); - filter: drop-shadow(0 0 5px rgba(var(--fitme-accent-rgb), .9)) drop-shadow(0 0 14px rgba(var(--fitme-accent-rgb), .55)); + box-shadow: var(--neon-face-on); } .trainer-item .trainer-pic:hover::before { opacity: 1; } -.trainer-item .trainer-pic:hover::after { - box-shadow: inset 0 0 14px 1px rgba(var(--fitme-accent-rgb), .4), inset 0 0 3px 0 rgba(var(--fitme-accent-rgb), .6); -} +.trainer-item .trainer-pic:hover::after { box-shadow: var(--face-light); } @media (hover: hover) and (prefers-reduced-motion: no-preference) { - .trainer-item .trainer-pic { transition: transform .25s cubic-bezier(.3, .7, .4, 1.5), box-shadow .45s ease, filter .45s ease; } + .trainer-item .trainer-pic { transition: transform .25s cubic-bezier(.3, .7, .4, 1.5), box-shadow .38s ease-out; } .trainer-item .trainer-pic:hover { animation: neon-flicker-glow 600ms linear 1.4s 1; } .trainer-item .trainer-pic:hover::before { animation: neon-flicker-tube 600ms linear 1.4s 1; } } -/* Hover: white text on an almost-black face keeping ~10% of the brand colour — - the bright neon faces around it then stand out against it. The dark and - ghost variants keep their own backgrounds. */ +/* Hover: white text on an almost-black face keeping ~10% of the brand colour + (white on it is >= 15:1 for any brand colour, even pure white or black). + The lettering gets a dark backing under the neon text-shadow so very light + brand colours never wash the letter edges out. The dark and ghost variants + keep their own backgrounds. */ .site-btn:not(.sb-dark):not(.sb-line):not(.sb-ghost):hover, .site-btn:not(.sb-dark):not(.sb-line):not(.sb-ghost):focus-visible, .gym-switch-btn:hover, @@ -552,7 +605,7 @@ section[id] { background: color-mix(in srgb, var(--fitme-accent) 10%, #000); border-color: color-mix(in srgb, var(--fitme-accent) 10%, #000); color: #fff; - text-shadow: 0 0 8px rgba(var(--fitme-accent-rgb), .9), 0 0 2px rgba(255, 255, 255, .6); /* neon lettering */ + text-shadow: 0 0 1px rgba(0, 0, 0, .8), 0 0 7px rgba(var(--fitme-accent-rgb), .75); /* neon lettering */ } .site-btn.sb-dark:hover, .site-btn.sb-line:hover { color: #fff; }