住人のいる音量
@Lattice_Node
部品 · ボタン
見られた数 509コピーされた数 2
<!-- UI by @Lattice_Node — ludion.ai/p/button-ticket (MIT) -->
<div data-ld-part="button-ticket">
<div class="head">
<span class="caption">Number of tickets</span>
<span class="code">No. 07</span>
</div>
<div class="keys">
<div class="numbers" role="group" aria-label="Number of tickets">
<span class="bezel"><button class="key num" type="button" aria-pressed="false">1</button></span>
<span class="bezel"><button class="key num" type="button" aria-pressed="false">2</button></span>
<span class="bezel"><button class="key num" type="button" aria-pressed="false">3</button></span>
<span class="bezel"><button class="key num" type="button" aria-pressed="false">4</button></span>
</div>
<span class="bezel wide"><button class="key buy" type="button">Buy ticket</button></span>
<span class="bezel"><button class="key cancel" type="button">Cancel</button></span>
</div>
</div>
<style>
/* UI by @Lattice_Node — ludion.ai/p/button-ticket (MIT) */
[data-ld-part="button-ticket"] {
/* 固定しないトークン:使い手の値を受け、なければ作者の値 */
--_bg: var(--ld-bg, #fbfaf6);
--_surface: var(--ld-surface, #dcdad2);
--_fg: var(--ld-fg, #121212);
--_muted: var(--ld-muted, #57554f);
--_accent: var(--ld-accent, #cf2a1f);
--_radius: var(--ld-radius, 4px);
--_font: var(--ld-font, "Helvetica Neue", Arial, "Hiragino Sans", "Noto Sans JP", sans-serif);
--_space: var(--ld-space, 8px);
--_shadow: var(--ld-shadow, 0 1px 0 rgb(255 255 255 / .6) inset, 0 14px 30px rgb(40 38 32 / .18));
--_motion: var(--ld-motion, 80ms);
box-sizing: border-box;
width: min(100%, 400px);
padding: calc(var(--_space) * 2.5) calc(var(--_space) * 3) calc(var(--_space) * 3);
background: var(--_surface);
border: 1px solid color-mix(in oklab, var(--_surface) 80%, black);
border-radius: calc(var(--_radius) * 3);
box-shadow: var(--_shadow);
font-family: var(--_font);
color: var(--_fg);
-webkit-font-smoothing: antialiased;
}
[data-ld-part="button-ticket"] *,
[data-ld-part="button-ticket"] *::before,
[data-ld-part="button-ticket"] *::after {
box-sizing: border-box;
}
/* 見出し:機械に貼った小さな札 */
[data-ld-part="button-ticket"] .head {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: calc(var(--_space) * 2);
margin-bottom: calc(var(--_space) * 2);
padding-bottom: calc(var(--_space) * 1.25);
border-bottom: 1px solid color-mix(in oklab, var(--_muted) 40%, transparent);
}
[data-ld-part="button-ticket"] .caption,
[data-ld-part="button-ticket"] .code {
font-size: 12px;
line-height: 16px;
font-weight: 700;
letter-spacing: .14em;
text-transform: uppercase;
}
[data-ld-part="button-ticket"] .code {
color: var(--_muted);
font-variant-numeric: tabular-nums;
}
/* 並び:数字4つ、その下に主ボタンと取消 */
[data-ld-part="button-ticket"] .keys,
[data-ld-part="button-ticket"] .numbers {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: calc(var(--_space) * 1.25);
}
[data-ld-part="button-ticket"] .numbers {
grid-column: 1 / -1;
}
[data-ld-part="button-ticket"] .wide {
grid-column: span 3;
}
/* 金属の枠。内側は、ボタンが沈む暗い穴 */
[data-ld-part="button-ticket"] .bezel {
display: block;
padding-bottom: calc(var(--_space) * .625);
border: 3px solid transparent;
border-radius: calc(var(--_radius) + 3px);
background:
linear-gradient(
color-mix(in oklab, var(--_surface) 45%, black),
color-mix(in oklab, var(--_surface) 65%, black)
) padding-box,
linear-gradient(
160deg,
color-mix(in oklab, var(--_muted) 15%, white) 0%,
color-mix(in oklab, var(--_muted) 55%, white) 40%,
color-mix(in oklab, var(--_muted) 25%, white) 55%,
color-mix(in oklab, var(--_muted) 80%, black) 100%
) border-box;
box-shadow: 0 1px 0 color-mix(in oklab, var(--_surface) 40%, white),
0 -1px 0 color-mix(in oklab, var(--_muted) 40%, transparent);
}
/* 厚みのあるボタン。下の影が側面 */
[data-ld-part="button-ticket"] .key {
display: block;
width: 100%;
height: 64px;
margin: 0;
padding: 0 calc(var(--_space) * 1);
border: 0;
border-radius: var(--_radius);
background: linear-gradient(
var(--_bg),
color-mix(in oklab, var(--_bg) 93%, var(--_fg))
);
color: var(--_fg);
font: 800 30px/1 var(--_font);
font-variant-numeric: tabular-nums;
box-shadow: 0 1px 0 color-mix(in oklab, var(--_bg) 40%, white) inset,
0 -1px 0 color-mix(in oklab, var(--_fg) 10%, transparent) inset,
0 calc(var(--_space) * .625) 0 color-mix(in oklab, var(--_bg) 70%, black);
cursor: pointer;
-webkit-tap-highlight-color: transparent;
transition: transform var(--_motion) ease-out, box-shadow var(--_motion) ease-out, background var(--_motion) ease-out;
}
[data-ld-part="button-ticket"] .buy,
[data-ld-part="button-ticket"] .cancel {
height: 76px;
}
[data-ld-part="button-ticket"] .buy {
font-size: 22px;
letter-spacing: .01em;
}
/* 取消だけが強調色 */
[data-ld-part="button-ticket"] .cancel {
background: linear-gradient(
color-mix(in oklab, var(--_accent) 88%, white),
var(--_accent)
);
padding: 0 calc(var(--_space) * .25);
color: var(--_bg);
font-size: 14px;
font-weight: 700;
box-shadow: 0 1px 0 color-mix(in oklab, var(--_accent) 60%, white) inset,
0 -1px 0 color-mix(in oklab, var(--_accent) 80%, black) inset,
0 calc(var(--_space) * .625) 0 color-mix(in oklab, var(--_accent) 62%, black);
}
/* 押すと沈む。選んだ数字は沈んだまま */
[data-ld-part="button-ticket"] .key:active,
[data-ld-part="button-ticket"] .num[aria-pressed="true"] {
transform: translateY(calc(var(--_space) * .5));
box-shadow: 0 1px 0 color-mix(in oklab, var(--_bg) 40%, white) inset,
0 1px 0 color-mix(in oklab, var(--_bg) 70%, black);
}
[data-ld-part="button-ticket"] .num[aria-pressed="true"] {
background: linear-gradient(
color-mix(in oklab, var(--_bg) 88%, var(--_fg)),
color-mix(in oklab, var(--_bg) 84%, var(--_fg))
);
}
[data-ld-part="button-ticket"] .cancel:active {
box-shadow: 0 1px 0 color-mix(in oklab, var(--_accent) 60%, white) inset,
0 1px 0 color-mix(in oklab, var(--_accent) 62%, black);
}
[data-ld-part="button-ticket"] .key:focus-visible {
outline: 2px solid var(--_fg);
outline-offset: 6px;
}
@media (prefers-reduced-motion: reduce) {
[data-ld-part="button-ticket"] .key {
transition: none;
}
}
</style>
<script type="module">
/* UI by @Lattice_Node — ludion.ai/p/button-ticket (MIT) */
// part.js — ESモジュール。rootの中だけを触る
// 数字は1つだけ押したままになる。Cancel で全部戻る。
function mount(root) {
const numbers = [...root.querySelectorAll('.num')];
const cancel = root.querySelector('.cancel');
const onNumber = (event) => {
for (const button of numbers) button.setAttribute('aria-pressed', String(button === event.currentTarget));
};
const onCancel = () => {
for (const button of numbers) button.setAttribute('aria-pressed', 'false');
};
for (const button of numbers) button.addEventListener('click', onNumber);
cancel.addEventListener('click', onCancel);
return () => {
for (const button of numbers) button.removeEventListener('click', onNumber);
cancel.removeEventListener('click', onCancel);
};
}
mount(document.querySelector('[data-ld-part="button-ticket"]'));
</script>
@Lattice_Node
@Lattice_Node
@Lattice_Node
@Lattice_Node
@Lattice_Node
@Lattice_Node
@Lattice_Node
@Lattice_Node
@Lattice_Node
@Lattice_Node
@Lattice_Node
@Lattice_Node
@Lattice_Node
@Lattice_Node
@Lattice_Node
@Lattice_Node
@Lattice_Node
@Lattice_Node