/* ==========================================================================
   custom.css — project overrides on top of the Tokyo template
   --------------------------------------------------------------------------
   Loaded last so it wins over style.css / dark.css without editing vendor
   files. Keeping customisations here means the template can be re-downloaded
   and diffed if it ever needs updating.

   Contents
     1. Accessibility — skip link, focus, labels, reduced motion, targets
     2. Contrast fixes — measured WCAG 2.2 AA corrections
     3. Inline SVG icons — replaces the 3.2 MB Fontello webfont
     4. Theme toggle — single-page light/dark
     5. Form — honeypot, pending/sent states
   ========================================================================== */


/* ==========================================================================
   1. ACCESSIBILITY
   ========================================================================== */

/* Skip link — first focusable element, so keyboard users can jump the nav.
   Visible only on focus (WCAG 2.4.1 Bypass Blocks). */
.skip-link {
	position: absolute;
	top: -100px;
	left: 16px;
	z-index: 100000;
	padding: 12px 20px;
	background: #000;
	color: #fff;
	font-family: "Montserrat", sans-serif;
	font-size: 14px;
	font-weight: 600;
	text-decoration: none;
	border-radius: 0 0 4px 4px;
	transition: top .15s ease-in-out;
}

.skip-link:focus {
	top: 0;
}

/* The template ships no focus styling at all, which makes keyboard navigation
   invisible. :focus-visible keeps the ring for keyboard users without showing
   it on mouse click (WCAG 2.4.7 Focus Visible). */
a:focus-visible,
button:focus-visible,
input:focus-visible,
textarea:focus-visible,
[tabindex]:focus-visible {
	outline: 3px solid #0a66c2;
	outline-offset: 3px;
	border-radius: 2px;
}

body.dark a:focus-visible,
body.dark button:focus-visible,
body.dark input:focus-visible,
body.dark textarea:focus-visible,
body.dark [tabindex]:focus-visible {
	outline-color: #6fb4ff;
}

/* Persistent labels remain readable after a visitor starts typing. */
.contact_form label {
	display: block;
	font-weight: 700;
	margin-bottom: 8px;
	color: #444;
}
body.dark .contact_form label { color: #eee; }

/* Interactive targets need to be at least 24x24 CSS px (WCAG 2.2, 2.5.8).
   The social icon rows sit well under that at their default font size. */
.tokyo_tm_home .social ul li a,
.tokyo_tm_modalbox .close a,
.tokyo_tm_portfolio .portfolio_filter ul li a {
	min-width: 24px;
	min-height: 24px;
	display: inline-flex;
	align-items: center;
	justify-content: center;
}

/* Honour the OS "reduce motion" setting. The template runs a preloader, page
   transitions, a custom cursor, carousels and animated progress bars with no
   regard for it, which can trigger symptoms for users with vestibular
   disorders (WCAG 2.3.3). Animations are reduced to a near-instant step rather
   than removed outright, so anything relying on an animation/transition end
   event still fires. */
@media (prefers-reduced-motion: reduce) {
	*,
	*::before,
	*::after {
		animation-duration: .01ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: .01ms !important;
		scroll-behavior: auto !important;
	}

	.mouse-cursor {
		display: none !important;
	}
}

/* The custom cursor is meaningless without a pointer and can suppress the
   system cursor people rely on. Hide it wherever there is no fine pointer. */
@media (hover: none), (pointer: coarse) {
	.mouse-cursor {
		display: none !important;
	}
}

/* The preloader fades out over ~2s but stays in the DOM as a full-screen layer
   until it is removed, swallowing every click underneath it in the meantime.
   Once it has started fading it should not intercept input. */
#preloader.preloaded {
	pointer-events: none;
}


/* ==========================================================================
   2. CONTRAST FIXES (WCAG 2.2 AA — 1.4.3 Contrast Minimum)
   --------------------------------------------------------------------------
   Every value below was measured against the background it actually renders
   on, and replaced with the nearest visually-equivalent colour that clears
   4.5:1. Ratios are noted so they can be re-verified.
   ========================================================================== */

/* Footer copyright: #999 on #fff measured 2.85:1 — failed at any text size. */
.tokyo_tm_all_wrap .leftpart .copyright p {
	color: #6a6a6a;  /* 5.41:1 */
}

/* Footer link: #787878 measured 4.42:1 — just under the threshold. */
.tokyo_tm_all_wrap .leftpart .copyright a {
	color: #6a6a6a;  /* 5.41:1 */
}

/* Modal body copy: #888 measured 3.54:1. */
.tokyo_tm_modalbox .main_content .descriptions .bigger {
	color: #6a6a6a;  /* 5.41:1 */
}

/* Form validation messages: the brand red measured 4.09:1 on white. Error text
   is the worst possible place for low contrast, since it is what a user reads
   when something has already gone wrong. Darkened while keeping the hue. */
.tokyo_tm_contact .empty_notice,
.tokyo_tm_contact .contact_error,
.contact_form .contact_error {
	color: #d41518;  /* 5.36:1 */
}

/* Hero tagline: #767676 measured 4.27:1 here. Note this one sits on #f8f8f8,
   not pure white, which is exactly why it failed while the same colour passes
   elsewhere -- contrast has to be measured against the actual backdrop.
   The dark theme needs its own value; darkening for light would drop it to
   2.26:1 against the dark background. */
.tokyo_tm_home .details .job {
	color: #6a6a6a;  /* 5.09:1 on #f8f8f8 */
}

body.dark .tokyo_tm_home .details .job {
	color: #a5a5a5;  /* 4.97:1 on #34353a */
}

/* Dark theme: section eyebrow text measured 4.08:1 on #34353a. */
body.dark .tokyo_tm_title span {
	color: #a5a5a5;  /* 4.97:1 */
}

/* Dark theme: the same brand red drops to 2.99:1 against the dark background,
   so it needs its own lighter value rather than inheriting the light one. */
body.dark .tokyo_tm_contact .empty_notice,
body.dark .tokyo_tm_contact .contact_error,
body.dark .contact_form .contact_error {
	color: #ff7b7d;  /* 4.88:1 */
}


/* ==========================================================================
   3. INLINE SVG ICONS
   --------------------------------------------------------------------------
   The six icons the page uses are now inline <svg><use> references to a
   sprite at the top of <body>, generated by tools/build-icons.py from the
   template's own font so the artwork is unchanged.

   Sizing with 1em and fill:currentColor makes each icon behave exactly like
   the glyph it replaced: it inherits whatever font-size and color the existing
   template CSS already sets on the parent, so no per-icon sizing is needed.
   ========================================================================== */

svg.ic {
	width: 1em;
	height: 1em;
	fill: currentColor;
	vertical-align: -.125em;
	flex: none;
	pointer-events: none;
}


/* ==========================================================================
   4. THEME TOGGLE
   --------------------------------------------------------------------------
   The template shipped light and dark as two near-identical HTML files, which
   meant every content edit had to be made twice and the two would inevitably
   drift. They are now one page; this control switches body.dark at runtime and
   js/app.js remembers the choice.
   ========================================================================== */

.theme-toggle {
	position: fixed;
	right: 20px;
	bottom: 20px;
	/* Above the site chrome, but deliberately below #preloader (99999) so the
	   loading screen still covers it while the page is coming up. */
	z-index: 9999;
	display: inline-flex;
	align-items: center;
	gap: 8px;
	min-height: 40px;
	padding: 10px 16px;
	border: 1px solid rgba(0, 0, 0, .18);
	border-radius: 999px;
	background: #fff;
	color: #1a1a1a;
	font-family: "Montserrat", sans-serif;
	font-size: 12px;
	font-weight: 600;
	letter-spacing: .4px;
	text-transform: uppercase;
	cursor: pointer;
	box-shadow: 0 4px 14px rgba(0, 0, 0, .14);
	transition: background .2s ease, color .2s ease, border-color .2s ease;
}

.theme-toggle:hover {
	border-color: rgba(0, 0, 0, .4);
}

body.dark .theme-toggle {
	background: #34353a;
	color: #f2f2f2;
	border-color: rgba(255, 255, 255, .28);
	box-shadow: 0 4px 14px rgba(0, 0, 0, .4);
}

body.dark .theme-toggle:hover {
	border-color: rgba(255, 255, 255, .55);
}

/* The wordmark is gone from both the sidebar and the mobile topbar: it was
   fixed, so it repeated in every section, and the hero already carries the
   full name. The topbar is `justify-content: space-between` for a logo and a
   hamburger; with the logo removed that single child falls to the left, so
   push it back to the edge it belongs on. */
.tokyo_tm_topbar .topbar_inner {
	justify-content: flex-end;
}

/* Theme is applied by the small, independent theme.js before content paints. */


/* ==========================================================================
   5. FORM
   ========================================================================== */

/* Honeypot. Kept out of the layout and out of the accessibility tree, but not
   display:none — some bots skip hidden inputs, and this one is meant to be
   tempting. Real people never see or tab to it. */
.hp-field {
	position: absolute !important;
	left: -9999px;
	width: 1px;
	height: 1px;
	overflow: hidden;
}

/* --------------------------------------------------------------------------
   Map facade — placeholder shown until the visitor asks for the embed.
   -------------------------------------------------------------------------- */

.map-facade {
	height: 355px;
	width: 100%;
	overflow: hidden;
}

.map-facade__btn {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 8px;
	width: 100%;
	height: 100%;
	padding: 24px;
	border: 1px dashed rgba(0, 0, 0, .25);
	background: #f3f3f3;
	color: #1a1a1a;
	font-family: "Montserrat", sans-serif;
	text-align: center;
	cursor: pointer;
}

.map-facade__btn:hover {
	background: #ececec;
}

.map-facade__title {
	font-size: 15px;
	font-weight: 700;
	letter-spacing: .04em;
	text-transform: uppercase;
}

.map-facade__note {
	max-width: 32ch;
	font-size: 13px;
	line-height: 1.5;
	color: #6a6a6a;  /* 5.0:1 on #f3f3f3 */
}

.map-facade.is-loaded {
	border: 0;
}

.map-facade iframe {
	display: block;
	border: 0;
}

body.dark .map-facade__btn {
	border-color: rgba(255, 255, 255, .3);
	background: #3d3f45;
	color: #f2f2f2;
}

body.dark .map-facade__btn:hover {
	background: #45474e;
}

body.dark .map-facade__note {
	color: #b4b4b4;  /* 5.5:1 on #3d3f45 */
}

/* ==========================================================================
   Buttons
   --------------------------------------------------------------------------
   The template renders its buttons as <a> elements with a filled background.
   The submit control is a real <button> now, which meant it lost that styling
   and rendered as bare text -- nothing about it said "clickable".

   These rules give every button the same affordance and the full set of states
   a control needs: rest, hover, active, focus-visible, and busy. A control that
   only has a resting state leaves people unsure whether anything happened.
   ========================================================================== */

.tokyo_tm_button a,
.tokyo_tm_button button {
	position: relative;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 10px;
	min-height: 48px;          /* comfortably over the 44px touch target */
	padding: 13px 34px;
	border: 1px solid #000;
	border-radius: 2px;
	background-color: #000;
	color: #fff;
	font-family: "Montserrat", sans-serif;
	font-size: 13px;
	font-weight: 600;
	letter-spacing: .09em;
	text-transform: uppercase;
	text-decoration: none;
	cursor: pointer;
	overflow: hidden;
	transition: background-color .22s ease, color .22s ease,
	            transform .12s ease, box-shadow .22s ease;
}

/* Hover lifts slightly and casts a shadow: movement toward the pointer reads as
   "this responds to you" far faster than a colour change alone. */
.tokyo_tm_button a:hover,
.tokyo_tm_button button:hover:not(:disabled) {
	background-color: #fff;
	color: #000;
	transform: translateY(-2px);
	box-shadow: 0 6px 18px rgba(0, 0, 0, .16);
}

/* Pressed state. Without it a click feels like nothing happened. */
.tokyo_tm_button a:active,
.tokyo_tm_button button:active:not(:disabled) {
	transform: translateY(0);
	box-shadow: 0 2px 6px rgba(0, 0, 0, .18);
}

.tokyo_tm_button button:disabled {
	cursor: progress;
	opacity: .75;
	transform: none;
	box-shadow: none;
}

body.dark .tokyo_tm_button a,
body.dark .tokyo_tm_button button {
	border-color: #fff;
	background-color: #fff;
	color: #1a1a1a;
}

body.dark .tokyo_tm_button a:hover,
body.dark .tokyo_tm_button button:hover:not(:disabled) {
	background-color: transparent;
	color: #fff;
}

/* The arrow is a direction cue, not decoration: it nudges the control toward
   reading as an action rather than a label. It leans in on hover. */
.tokyo_tm_button button::after,
.tokyo_tm_button a[download]::after {
	content: "";
	width: 14px;
	height: 10px;
	flex: none;
	background-color: currentColor;
	-webkit-mask: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 10"><path d="M9 0l5 5-5 5-1.4-1.4L10.2 6H0V4h10.2L7.6 1.4z"/></svg>') no-repeat center / contain;
	        mask: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 10"><path d="M9 0l5 5-5 5-1.4-1.4L10.2 6H0V4h10.2L7.6 1.4z"/></svg>') no-repeat center / contain;
	transition: transform .22s ease;
}

.tokyo_tm_button button:hover:not(:disabled)::after,
.tokyo_tm_button a[download]:hover::after {
	transform: translateX(4px);
}

/* While sending, the arrow becomes a spinner, so the control itself reports
   progress rather than the visitor having to find a message elsewhere. */
.tokyo_tm_button button[aria-busy="true"]::after {
	width: 15px;
	height: 15px;
	background: none;
	-webkit-mask: none;
	        mask: none;
	border: 2px solid currentColor;
	border-top-color: transparent;
	border-radius: 50%;
	animation: btn-spin .7s linear infinite;
	transform: none;
}

@keyframes btn-spin {
	to { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
	.tokyo_tm_button a:hover,
	.tokyo_tm_button button:hover:not(:disabled) {
		transform: none;
	}

	.tokyo_tm_button button[aria-busy="true"]::after {
		animation-duration: 1.4s;
	}
}

.contact_form .contact_success {
	color: #1d7a37;
}

body.dark .contact_form .contact_success {
	color: #7ddc96;
}

/* --------------------------------------------------------------------------
   Map container — relocated from inline <style> blocks that the embed
   generator left inside a <div>, which is invalid HTML.
   -------------------------------------------------------------------------- */

.mapouter {
	position: relative;
	text-align: right;
	width: 100%;
	height: 355px;
}

.gmap_canvas {
	width: 100%;
	height: 355px;
	overflow: hidden;
	background: none !important;
}

/* --------------------------------------------------------------------------
   Disclosure marquee — "Reported vulnerabilities to"
   --------------------------------------------------------------------------
   A continuously scrolling logo rail. Auto-moving content is easy to get wrong,
   so the rules it follows are deliberate:

     - It pauses on hover AND on focus-within, so a keyboard user can stop it
       the same way a mouse user can (WCAG 2.2.2 gives a way to pause moving
       content; hover alone does not serve keyboard users).
     - prefers-reduced-motion turns it into a static wrapped grid rather than
       just slowing it. Motion that cannot be escaped is the actual problem.
     - The track is duplicated so the loop is seamless; the copy is aria-hidden
       so each organisation is announced once.
     - Names stay visible under every mark. Several of these are wordless
       glyphs, and nobody should have to guess which company a tile means.
     - Edge masks fade the rail out rather than cutting logos mid-stroke.
   -------------------------------------------------------------------------- */

.tokyo_tm_partners .org-note {
	margin: 10px 0 30px;
	font-size: 15px;
	line-height: 1.6;
	color: #6a6a6a;
}

body.dark .tokyo_tm_partners .org-note {
	color: #c8c8c8;
}

.org-marquee {
	--marquee-duration: 38s;
	width: 100%;
	overflow: hidden;
	padding: 6px 0;
	/* Fade both ends instead of a hard crop. */
	-webkit-mask-image: linear-gradient(90deg, transparent, #000 9%, #000 91%, transparent);
	mask-image: linear-gradient(90deg, transparent, #000 9%, #000 91%, transparent);
}

.org-marquee__track {
	display: flex;
	width: max-content;
	animation: org-scroll var(--marquee-duration) linear infinite;
}

/* Two identical groups: when the first has travelled exactly its own width,
   the second sits where it started, so the reset is invisible. */
@keyframes org-scroll {
	from { transform: translate3d(0, 0, 0); }
	to   { transform: translate3d(-50%, 0, 0); }
}

.org-marquee:hover .org-marquee__track,
.org-marquee:focus-within .org-marquee__track {
	animation-play-state: paused;
}

/* The template styles its partner grid with 25%-wide floated, bordered cells
   (.tokyo_tm_partners ul li). Those rules still match the marquee's lists and
   would otherwise reimpose a grid with dividers on top of the rail. */
.tokyo_tm_partners .org-marquee ul,
.tokyo_tm_partners .org-marquee ul li {
	float: none;
	width: auto;
	height: auto;
	line-height: inherit;
	border: 0;
	margin: 0;
	overflow: visible;
}

.org-marquee__group {
	display: flex;
	flex: none;
	align-items: center;
	margin: 0;
	padding: 0;
	list-style: none;
}

/* Scoped to match the weight of the neutralising rule above, which would
   otherwise reimpose width:auto and let long names stretch their tile. */
.tokyo_tm_partners .org-marquee ul li.org {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 14px;
	flex: none;
	width: 186px;
	margin: 0;
	padding: 20px 14px;
	text-align: center;
}

.tokyo_tm_partners .org-marquee ul li .org-logo {
	width: 62px;
	height: 62px;
	max-width: none;
	object-fit: contain;
	/* Seven different brand palettes side by side fight each other and the
	   page. Desaturated at rest, true colour when the rail is paused. */
	filter: grayscale(1);
	opacity: .72;
	transition: filter .25s ease, opacity .25s ease, transform .25s ease;
}

.org-marquee:hover .org-logo,
.org-marquee:focus-within .org-logo {
	filter: grayscale(0);
	opacity: 1;
}

.org-marquee .org:hover .org-logo {
	transform: scale(1.06);
}

.org-name {
	font-family: "Montserrat", sans-serif;
	font-size: 12px;
	font-weight: 700;
	line-height: 1.4;
	letter-spacing: .07em;
	text-transform: uppercase;
	color: #6a6a6a;          /* 5.41:1 on #fff */
	transition: color .25s ease;
}

.org-marquee:hover .org-name,
.org-marquee:focus-within .org-name {
	color: #1a1a1a;
}

body.dark .org-name {
	color: #c8c8c8;
}

body.dark .org-marquee:hover .org-name,
body.dark .org-marquee:focus-within .org-name {
	color: #ffffff;
}

/* Marks drawn for light backgrounds vanish on a dark one. Give each a light
   plate rather than inverting it and wrecking the brand colour. */
body.dark .org-logo {
	background: #f4f4f4;
	border-radius: 10px;
	padding: 5px;
}

/* Someone who asked for less motion gets no motion -- the rail becomes a
   static, wrapped grid, and the duplicate group is removed so nothing repeats. */
@media (prefers-reduced-motion: reduce) {
	.org-marquee {
		-webkit-mask-image: none;
		mask-image: none;
	}

	.org-marquee__track {
		animation: none;
		width: 100%;
		flex-wrap: wrap;
		justify-content: center;
	}

	.org-marquee__group {
		flex-wrap: wrap;
		justify-content: center;
		width: 100%;
	}

	.org-marquee__group[aria-hidden="true"] {
		display: none;
	}

	.org-logo {
		filter: grayscale(.35);
		opacity: 1;
	}

	.org-name {
		color: #1a1a1a;
	}

	body.dark .org-name {
		color: #f2f2f2;
	}
}

@media (max-width: 768px) {
	.org-marquee {
		--marquee-duration: 26s;
	}

	.tokyo_tm_partners .org-marquee ul li.org {
		width: 146px;
		padding: 16px 10px;
	}

	.tokyo_tm_partners .org-marquee ul li .org-logo {
		width: 50px;
		height: 50px;
	}
}

/* --------------------------------------------------------------------------
   Writeups — excerpt and tags, added by the live Medium feed.
   -------------------------------------------------------------------------- */

.writeup-excerpt {
	margin: 10px 0 0;
	font-size: 15px;
	line-height: 1.62;
	color: #6a6a6a;  /* 5.41:1 on #fff */
}

body.dark .writeup-excerpt {
	color: #ccc;  /* Readable on the theme's #494b50 article surface. */
}

.writeup-tags {
	display: flex;
	flex-wrap: wrap;
	gap: 8px;
	margin: 14px 0 0;
	padding: 0;
	list-style: none;
}

.writeup-tags li {
	margin: 0;
	/* Feed tags are arbitrary strings; "penetration-testing" must stay on one
	   line rather than wrapping into a tall two-line pill. */
	flex: 0 0 auto;
	white-space: nowrap;
	padding: 4px 11px;
	border: 1px solid #e2e2e2;
	border-radius: 999px;
	font-family: "Montserrat", sans-serif;
	font-size: 11px;
	font-weight: 600;
	letter-spacing: .05em;
	text-transform: uppercase;
	color: #6a6a6a;
}

body.dark .writeup-tags li {
	border-color: #4b4d53;
	color: #b4b4b4;
}

/* Not every Medium post has a lead image. A flat grey block reads as a broken
   image, so give those a deliberate panel instead. */
.writeup-noimage {
	background: linear-gradient(135deg, #ececec 0%, #f7f7f7 55%, #e6e6e6 100%);
	position: relative;
}

.writeup-noimage::after {
	content: "";
	position: absolute;
	inset: 0;
	background:
		repeating-linear-gradient(
			45deg,
			rgba(0, 0, 0, .035) 0 2px,
			transparent 2px 9px
		);
}

body.dark .writeup-noimage {
	background: linear-gradient(135deg, #3c3e44 0%, #44464d 55%, #393b41 100%);
}

body.dark .writeup-noimage::after {
	background:
		repeating-linear-gradient(
			45deg,
			rgba(255, 255, 255, .04) 0 2px,
			transparent 2px 9px
		);
}

/* ==========================================================================
   Contrast and target-size fixes found by the axe + Playwright sweep.
   Each value is measured against the background it actually renders on.
   ========================================================================== */

/* About subtitle: #767676 measured 4.27:1 on #f8f8f8. */
.about_title span {
	color: #6a6a6a;  /* 5.09:1 */
}

/* Resume dates: #767676 measured 4.05:1 on the #f2f2f2 panel. */
.tokyo_tm_resume_list .time span {
	color: #656565;  /* 5.21:1 */
}

/* Pricing feature rows: inactive items were #bbb on white -- 1.91:1, which is
   barely legible for anyone, not only as a conformance failure. */
.tokyo_tm_pricing .list ul.item li p {
	color: #6a6a6a;  /* 5.41:1 */
}

body.dark .about_title span,
body.dark .tokyo_tm_resume_list .time span,
body.dark .tokyo_tm_pricing .list ul.item li p {
	color: #a5a5a5;  /* 4.97:1 on #34353a */
}

/* An inline link inside a paragraph must be distinguishable by something other
   than colour (WCAG 1.4.1). Underlining it satisfies that outright, instead of
   hunting for a blue that clears 3:1 against the surrounding grey. */
.org-note a {
	color: #1a4f8a;
	text-decoration: underline;
	text-underline-offset: 2px;
}

body.dark .org-note a {
	color: #8fc2ff;
}

/* Main navigation: the links rendered 47x19, under the 24px minimum for a
   pointer target (WCAG 2.2, 2.5.8). Padding rather than font-size, so the
   visual rhythm of the sidebar is unchanged. */
.tokyo_tm_all_wrap .leftpart .menu ul li a,
.tokyo_tm_mobile_menu .menu_list ul li a,
.transition_link li a {
	display: inline-block;
	min-height: 24px;
	padding-top: 3px;
	padding-bottom: 3px;
}

/* The skip link is measured the instant it receives focus, and an animated one
   is still off-screen at that point -- for a screen-reader user as much as for
   a test. It should simply be there. */
.skip-link {
	transition: none;
}

/* --------------------------------------------------------------------------
   Second contrast pass — values measured against the real rendered
   backgrounds, which are not the ones assumed first time round. The section
   eyebrow sits on #3c3d42 in dark mode, not the page background, and the
   disclosure note sits on a #494b50 panel.
   -------------------------------------------------------------------------- */

/* Pricing feature rows. The template's own selector out-specifies a plain
   class, so match its weight. */
.tokyo_tm_pricing .list ul li .list_inner ul.item li p {
	color: #6a6a6a;  /* 5.41:1 on #fff */
}

body.dark .tokyo_tm_pricing .list ul li .list_inner ul.item li p {
	color: #b8b8b8;
}

/* Portfolio filter buttons: #767676 on the #f8f8f8 panel measured 4.27:1. */
.tokyo_tm_portfolio .portfolio_filter ul li button {
	color: #6a6a6a;  /* 5.09:1 on #f8f8f8 */
}

.tokyo_tm_portfolio .portfolio_filter ul li button.current {
	color: #1a1a1a;
}

body.dark .tokyo_tm_portfolio .portfolio_filter ul li button {
	color: #b8b8b8;
}

body.dark .tokyo_tm_portfolio .portfolio_filter ul li button.current {
	color: #ffffff;
}

/* Section eyebrow ("ABOUT", "SERVICES", …) on its #3c3d42 chip in dark mode. */
body.dark .tokyo_tm_title .title_flex .left span {
	color: #bdbdbd;  /* 5.77:1 on #3c3d42 */
}

/* Disclosure note and its link, on the #494b50 panel. */
body.dark .tokyo_tm_partners .org-note {
	color: #c8c8c8;  /* 5.22:1 on #494b50 */
}

body.dark .org-note a {
	color: #a8d2ff;  /* 5.54:1 on #494b50 */
}

/* --------------------------------------------------------------------------
   "How to work with me": one panel, one call to action.
   --------------------------------------------------------------------------
   This was three pricing cards. They implied three products at three prices,
   when in truth everything here is quoted per enquiry, and all three carried
   the same "Get in touch" button pointing at the same anchor -- so the choice
   was decorative, and a decorative choice still costs the reader a decision.

   The template's pricing CSS is not reused: .price and .plan carry meaning
   this block no longer has. Purpose-built markup instead, so nothing has to
   be overridden back out.
   -------------------------------------------------------------------------- */

.work_panel {
	width: 100%;
	height: auto;
	clear: both;
	float: left;
	border: 1px solid rgba(0, 0, 0, .1);
	padding: 44px 48px 46px;
}

.work_panel__lead {
	/* Measure, not width: long lines are what make an intro go unread. */
	max-width: 68ch;
	margin: 0 0 38px;
	color: #4a4a4a;  /* 8.86:1 on #fff */
	font-size: 17px;
	line-height: 1.7;
}

.work_panel__list {
	display: grid;
	/* Two up on desktop, one up once a column would drop under ~16 words a
	   line. auto-fit rather than a breakpoint, so it reflows with the sidebar
	   rather than against a guessed viewport. */
	grid-template-columns: repeat(auto-fit, minmax(270px, 1fr));
	gap: 30px 48px;
	margin: 0 0 42px;
	padding: 0;
	list-style: none;
}

.work_panel__list li {
	margin: 0;
	padding: 0;
}

.work_panel__list h4 {
	font-family: "Poppins", sans-serif;
	font-size: 17px;
	font-weight: 600;
	color: #1a1a1a;
	margin: 0 0 9px;
}

.work_panel__list p {
	margin: 0;
	color: #6a6a6a;  /* 5.41:1 on #fff */
	line-height: 1.65;
}

.work_panel__cta {
	display: flex;
	align-items: center;
	flex-wrap: wrap;
	gap: 16px 28px;
	border-top: 1px solid rgba(0, 0, 0, .1);
	padding-top: 34px;
}

/* .tokyo_tm_button is width:100%/float:left for the template's stacked
   layout, which would push the note onto its own row and strand it. */
.work_panel__cta .tokyo_tm_button {
	width: auto;
	float: none;
	clear: none;
}

.work_panel__note {
	margin: 0;
	flex: 1 1 280px;
	color: #6a6a6a;  /* 5.41:1 on #fff */
	font-size: 14px;
	line-height: 1.6;
}

/* Dark mode is a separate pass, not an inversion: the panel sits on #494b50
   there, so every value above is re-measured against it rather than lightened
   by eye. */
body.dark .work_panel {
	border-color: rgba(255, 255, 255, .2);
}

body.dark .work_panel__lead {
	color: #e4e4e4;  /* 6.87:1 on #494b50 */
}

body.dark .work_panel__list h4 {
	color: #ffffff;
}

body.dark .work_panel__list p,
body.dark .work_panel__note {
	color: #c8c8c8;  /* 5.22:1 on #494b50 */
}

body.dark .work_panel__cta {
	border-top-color: rgba(255, 255, 255, .2);
}

@media (max-width: 768px) {
	.work_panel {
		padding: 32px 24px 34px;
	}
}

/* About body copy. #767676 clears 4.5:1 on white but only reaches 4.27:1 on the
   #f8f8f8 panel the text sits on at narrow widths -- the same colour passing on
   desktop and failing on mobile. Set a value that clears both. */
.about_text p {
	color: #6a6a6a;  /* 5.41:1 on #fff, 5.09:1 on #f8f8f8 */
}

body.dark .about_text p {
	color: #b8b8b8;
}

/* ==========================================================================
   Project cards — the software side of the portfolio
   --------------------------------------------------------------------------
   These replace the template's image tiles. A repository is not a photograph:
   what tells you whether it is worth opening is the name, what it does, and
   what it is built with. A thumbnail of a README tells you nothing, so the card
   leads with text and treats the stack as the visual signal.
   ========================================================================== */

.tokyo_tm_portfolio .portfolio_list > li .inner {
	height: 100%;
}

.project {
	display: flex;
	flex-direction: column;
	height: 100%;
	padding: 28px 26px 24px;
	border: 1px solid #e6e6e6;
	border-radius: 4px;
	background: #fff;
	transition: border-color .22s ease, box-shadow .22s ease, transform .22s ease;
}

/* The whole card lifts, so it reads as one object rather than a box that
   happens to contain a link. */
.project:hover {
	border-color: #c9c9c9;
	box-shadow: 0 10px 30px rgba(0, 0, 0, .09);
	transform: translateY(-3px);
}

.project__head {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: 14px;
	margin-bottom: 14px;
}

.project__name {
	margin: 0;
	font-family: "Montserrat", sans-serif;
	font-size: 19px;
	font-weight: 700;
	letter-spacing: .01em;
}

.project__name a {
	color: #1a1a1a;
	text-decoration: none;
}

.project__name a:hover {
	text-decoration: underline;
	text-underline-offset: 3px;
}

/* Language chip, colour-coded the way GitHub does it, so the stack is readable
   at a glance instead of requiring the label to be read. */
.project__lang {
	display: inline-flex;
	align-items: center;
	gap: 7px;
	flex: none;
	font-family: "Montserrat", sans-serif;
	font-size: 11px;
	font-weight: 600;
	letter-spacing: .06em;
	text-transform: uppercase;
	color: #6a6a6a;
	white-space: nowrap;
}

.project__lang::before {
	content: "";
	width: 10px;
	height: 10px;
	border-radius: 50%;
	background: #8a8a8a;
}

.project__lang[data-lang="python"]::before { background: #3572a5; }
.project__lang[data-lang="javascript"]::before { background: #b89b2b; }
.project__lang[data-lang="typescript"]::before { background: #2b7489; }
.project__lang[data-lang="go"]::before { background: #00add8; }
.project__lang[data-lang="rust"]::before { background: #b7410e; }
.project__lang[data-lang="html"]::before { background: #c6563c; }

.project__desc {
	margin: 0 0 18px;
	font-size: 15px;
	line-height: 1.68;
	color: #5f5f5f;          /* 6.4:1 on #fff */
}

.project__tags {
	display: flex;
	flex-wrap: wrap;
	gap: 7px;
	margin: 0 0 20px;
	padding: 0;
	list-style: none;
}

.project__tags li {
	margin: 0;
	padding: 4px 10px;
	border: 1px solid #e2e2e2;
	border-radius: 999px;
	font-family: "Montserrat", sans-serif;
	font-size: 11px;
	font-weight: 600;
	letter-spacing: .04em;
	color: #6a6a6a;
	white-space: nowrap;
}

/* Pushed to the bottom so every card's call to action lines up, regardless of
   how long its description runs. */
.project__link {
	display: inline-flex;
	align-items: center;
	gap: 8px;
	margin-top: auto;
	font-family: "Montserrat", sans-serif;
	font-size: 12px;
	font-weight: 700;
	letter-spacing: .09em;
	text-transform: uppercase;
	color: #1a1a1a;
	text-decoration: none;
}

.project__link::after {
	content: "";
	width: 13px;
	height: 9px;
	background-color: currentColor;
	-webkit-mask: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 10"><path d="M9 0l5 5-5 5-1.4-1.4L10.2 6H0V4h10.2L7.6 1.4z"/></svg>') no-repeat center / contain;
	        mask: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 10"><path d="M9 0l5 5-5 5-1.4-1.4L10.2 6H0V4h10.2L7.6 1.4z"/></svg>') no-repeat center / contain;
	transition: transform .22s ease;
}

.project:hover .project__link::after {
	transform: translateX(4px);
}

.project__link:hover {
	text-decoration: underline;
	text-underline-offset: 4px;
}

body.dark .project {
	border-color: #4b4d53;
	background: #3c3d42;
}

body.dark .project:hover {
	border-color: #6a6d75;
	box-shadow: 0 10px 30px rgba(0, 0, 0, .4);
}

body.dark .project__name a { color: #ffffff; }
body.dark .project__desc { color: #c4c4c4; }
body.dark .project__lang { color: #b4b4b4; }
body.dark .project__tags li { border-color: #5a5c62; color: #c0c0c0; }
body.dark .project__link { color: #ffffff; }

@media (prefers-reduced-motion: reduce) {
	.project:hover { transform: none; }
	.project:hover .project__link::after { transform: none; }
}

@media (max-width: 480px) {
	.project { padding: 22px 20px 20px; }
	.project__head { flex-direction: column; align-items: flex-start; gap: 8px; }
}

/* The template sizes portfolio grid cells with floated, percentage-width <li>
   rules. Those also match the <li>s inside a card's tag list, which is why the
   pills were floating into each other instead of sitting in a row. */
.tokyo_tm_portfolio .portfolio_list > li .project__tags,
.tokyo_tm_portfolio .portfolio_list > li .project__tags li {
	float: none;
	width: auto;
	height: auto;
	padding-left: 0;
	padding-right: 0;
	margin: 0;
	line-height: inherit;
}

.tokyo_tm_portfolio .portfolio_list > li .project__tags li {
	padding: 4px 10px;
}

/* ==========================================================================
   Writeup covers — generated, not photographed
   --------------------------------------------------------------------------
   Consistent dark panels identify each article's vulnerability class. The local
   HackerOne mark follows the portfolio's requested visual direction; article
   titles and Medium links carry the publication identity.
   ========================================================================== */

.writeup-cover {
	position: relative;
	box-sizing: border-box;
	min-width: 0;
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	justify-content: center;
	width: 100%;
	height: 100%;
	gap: 14px;
	padding: 28px 28px 56px;
	overflow: hidden;
	background: #14161b;
}

.writeup-cover__eyebrow {
	font-family: "Montserrat", sans-serif;
	font-size: 10px;
	font-weight: 600;
	line-height: 1.4;
	letter-spacing: .12em;
	text-transform: uppercase;
	color: #bfc1c9;
}

.writeup-cover__label {
	position: relative;
	min-width: 0;
	max-width: 100%;
	font-family: "Montserrat", sans-serif;
	font-size: clamp(24px, 3.2vw, 32px);
	font-weight: 800;
	line-height: 1.14;
	letter-spacing: .035em;
	text-align: left;
	text-transform: uppercase;
	color: #fff;
	overflow-wrap: anywhere;
}

/* A thin accent under the label is the one piece of colour, and it is the
   brand red rather than a per-post hue -- decoration that belongs to the site
   instead of to the individual card. */
.writeup-cover__label::after {
	content: "";
	display: block;
	width: 46px;
	height: 3px;
	margin: 16px 0 0;
	background: #d41518;
}

/* Scoped to beat `.tokyo_tm_news ul li .image img { min-width:100%; opacity:0 }`
   -- the template hides the <img> in a card's image slot because it uses it as
   an invisible aspect-ratio spacer, and the badge lives in that same slot. */
.tokyo_tm_news ul li .image .writeup-cover .writeup-cover__brand,
.writeup-cover__brand {
	position: absolute;
	right: 28px;
	bottom: 20px;
	width: 92px;
	min-width: 0;
	height: auto;
	opacity: .8;
	/* The logo file uses currentColor so it can be tinted wherever it is used
	   inline. An <img> has no page colour to inherit, so currentColor resolves
	   to black -- invisible on this cover. Forcing it white costs nothing and
	   keeps a single logo file for every context. */
	filter: brightness(0) invert(1);
	transition: opacity .25s ease;
}

.tokyo_tm_news ul li .list_inner:hover .writeup-cover .writeup-cover__brand {
	opacity: .9;
}

/* Hairline frame, so the cover reads as a deliberate label rather than a
   coloured block that failed to load. */
.writeup-cover::after {
	content: "";
	position: absolute;
	inset: 0;
	border: 1px solid rgba(255, 255, 255, .1);
	pointer-events: none;
}

.tokyo_tm_news ul li .list_inner:hover .writeup-cover {
	filter: none;
	transform: none;
}

@media (prefers-reduced-motion: reduce) {
	.tokyo_tm_news ul li .list_inner:hover .writeup-cover {
		filter: none;
	}
}

/* Same template-grid interference as the project cards: .tokyo_tm_news ul li
   rules also match the <li>s inside a card's tag list. */
.tokyo_tm_news ul li .writeup-tags,
.tokyo_tm_news ul li .writeup-tags li {
	float: none;
	width: auto;
	height: auto;
	margin: 0;
	padding-left: 0;
	padding-right: 0;
	line-height: inherit;
}

.tokyo_tm_news ul li .writeup-tags li {
	padding: 4px 11px;
}

.tokyo_tm_news ul li .writeup-tags {
	margin-top: 14px;
}


/* ==========================================================================
   Project filter controls
   --------------------------------------------------------------------------
   These are <button>s now rather than anchors: they change what is on screen,
   they do not navigate. The template styled anchors, so the look is restored
   here, plus the states a control needs.
   ========================================================================== */

.tokyo_tm_portfolio .portfolio_filter ul li button {
	padding: 6px 2px;
	border: 0;
	background: none;
	font-family: "Montserrat", sans-serif;
	font-size: 15px;
	font-weight: 500;
	line-height: 1.4;
	cursor: pointer;
	transition: color .2s ease;
}

/* The active filter is underlined as well as darker: colour alone is not a
   sufficient distinction (WCAG 1.4.1). */
.tokyo_tm_portfolio .portfolio_filter ul li button.current {
	font-weight: 700;
	text-decoration: underline;
	text-underline-offset: 6px;
	text-decoration-thickness: 2px;
}

.tokyo_tm_portfolio .portfolio_filter ul li button:hover {
	color: #1a1a1a;
}

body.dark .tokyo_tm_portfolio .portfolio_filter ul li button:hover {
	color: #ffffff;
}

/* Filtered-out cards. A transition would be nice, but these are floated grid
   cells -- animating them reflows the whole grid mid-transition and the result
   jitters. An instant, honest hide is better than a janky fade. */
.tokyo_tm_portfolio .portfolio_list > li.is-hidden {
	display: none;
}

.filter-status {
	position: absolute !important;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip: rect(0, 0, 0, 0);
	white-space: nowrap;
	border: 0;
}

/* --------------------------------------------------------------------------
   "All writeups on Medium"
   --------------------------------------------------------------------------
   Medium's RSS returns at most ten posts and offers no pagination, so the
   section cannot promise to hold everything. Rather than imply it does, this
   sends people to the full archive.
   -------------------------------------------------------------------------- */

/* A resume entry with no date still has to line up with the ones that do.
   The education entry has no start year on record, and an empty cell reading
   "TODO" on a live page is worse than no cell at all. */
.tokyo_tm_resume_list li .list_inner:not(:has(.time)) .place {
	margin-left: 0;
}

/* ==========================================================================
   Rendering cost
   --------------------------------------------------------------------------
   This template keeps every section in the DOM at the same coordinates and
   hides the inactive ones with visibility:hidden. Hidden is not the same as
   absent: the browser still styles, lays out and paints all six, which showed
   up as ~3.2s of Style & Layout in a Lighthouse trace once the writeups
   section grew to ten cards.

   content-visibility lets the browser skip that work for anything off screen
   and do it when the section is actually shown. contain-intrinsic-size gives
   it a size to assume meanwhile, so skipping does not cause layout shift --
   measured CLS stays at 0.011.
   ========================================================================== */

.tokyo_tm_news > ul > li {
	content-visibility: auto;
	contain-intrinsic-block-size: auto 420px;
	contain-intrinsic-inline-size: 0;
}

/* The marquee animates transform forever. Promoting it to its own layer keeps
   that off the main thread instead of repainting the section around it. */
.org-marquee__track {
	will-change: transform;
}

/* Paused means paused: drop the hint so an idle rail is not holding a
   composited layer for nothing. */
@media (prefers-reduced-motion: reduce) {
	.org-marquee__track {
		will-change: auto;
	}
}

/* Each cover paints two stacked gradients, times ten cards. Containing paint
   stops that work leaking into the surrounding layout. */
.writeup-cover {
	contain: paint;
}

/* ------------------------------------------------------------------ FACTS
   The template lays the facts strip out as `width: 33.333%; float: left`,
   which is fine for exactly three cards and strands the fourth on a row of
   its own. The strip now mixes two static software figures with however many
   the HackerOne function returns, so it needs to tolerate any count: grid
   with auto-fit does that and collapses to one column on a phone without
   needing the template's 768px override.

   Same specificity as the rules it replaces (0,3,1 / 0,3,2); custom.css
   loads last, so it wins without !important. */
.tokyo_tm_facts .list ul{
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
	gap: 40px;
	margin: 0;
}
.tokyo_tm_facts .list ul li{
	width: auto;
	float: none;
	margin: 0;
	padding-left: 0;
}
/* The inner box floats inside each card, which would collapse the grid
   item's height. */
.tokyo_tm_facts .list ul li .list_inner{
	float: none;
	height: 100%;
}

/* The About copy now names the four products and links each to its repository.
   The template sets `a { text-decoration: none }` globally and defines no
   colour for an inline link, so without this they render as plain body text --
   no affordance at all. Same treatment as .org-note a, for the same reason:
   measured against the body copy they sit in, these colours reach only 1.54:1
   (light) and 1.26:1 (dark), far under the 3:1 that would let colour carry the
   distinction alone, so the underline is what satisfies WCAG 1.4.1. */
.about_text a {
	color: #1a4f8a;  /* 8.30:1 on #fff, 7.82:1 on #f8f8f8 */
	text-decoration: underline;
	text-underline-offset: 2px;
}

body.dark .about_text a {
	color: #a8d2ff;  /* 5.54:1 on the #494b50 panel */
}

/* Content hierarchy: capabilities and case studies replace skill ratings. */
.hero-role {
	font-family: "Montserrat", sans-serif;
	font-weight: 700;
	font-size: 18px;
	color: #1a1a1a;
	margin: 0 0 14px;
}
.hero-location { font-size: 14px; margin: 0 0 20px; }
.content-band, .section-intro, .contact-intro, .hero-location { color: #5f5f5f; }
.hero-actions { display: flex; flex-wrap: wrap; gap: 12px 24px; margin-bottom: 24px; }
.hero-actions a {
	display: inline-flex;
	align-items: center;
	min-height: 44px;
	font-weight: 700;
}
.content-link, .hero-actions a {
	color: #1a4f8a;
	text-decoration: underline;
	text-underline-offset: 3px;
}
.content-band { clear: both; float: left; width: 100%; padding: 65px 0; background: #f8f8f8; }
.content-band--plain { background: transparent; }
.section-intro { max-width: 72ch; margin: 0 0 30px; }
.engineering-evidence .section-intro { margin-bottom: 0; }
.content-columns, .capability-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 40px; }
.capability-grid article { border-top: 1px solid #d6d6d6; padding-top: 22px; }
.capability-grid h4, .background-entry h4 { font-size: 18px; line-height: 1.5; margin: 0 0 12px; }
.capability-grid p { font-size: 15px; line-height: 1.75; margin: 0 0 12px; }
.capability-grid .capability-tools { color: #555; font-size: 13px; font-weight: 600; margin-bottom: 0; }
.content-list { margin: 0; padding-left: 20px; }
.content-list li { padding-left: 5px; margin-bottom: 12px; }
.background-entry { margin-bottom: 28px; }
/* Keep the section heading in normal flow, above the panel's border. */
.delivery-process .tokyo_section_title { float: none; margin-bottom: 28px; }
.delivery-panel { display: flow-root; clear: both; border: 1px solid #d6d6d6; padding: 36px; }
.delivery-panel__lead { max-width: 68ch; margin: 0 0 32px; color: #5f5f5f; }
.delivery-panel__list { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 32px; margin: 0 0 34px; padding: 0; list-style: none; }
.delivery-panel__list > li { display: grid; grid-template-columns: max-content minmax(0, 1fr); align-items: start; gap: 14px; margin: 0; padding: 0; }
.delivery-panel__number { color: #5f5f5f; font-size: 17px; font-weight: 600; font-variant-numeric: tabular-nums; line-height: 1.5; white-space: nowrap; }
.delivery-panel__list h4 { font-size: 17px; line-height: 1.5; margin-bottom: 10px; }
.delivery-panel__list p, .delivery-panel__note { color: #5f5f5f; line-height: 1.7; }
.delivery-panel__cta { display: flex; align-items: center; flex-wrap: wrap; gap: 18px 28px; border-top: 1px solid #d6d6d6; padding-top: 28px; }
.delivery-panel__cta .tokyo_tm_button { width: auto; max-width: 100%; float: none; margin-top: 0; }
.delivery-panel__note { margin-top: 20px; font-size: 14px; }
.delivery-panel__cta .delivery-panel__note { flex: 1 1 28ch; margin: 0; }
.contact-intro { margin-bottom: 36px; max-width: 72ch; }
.contact-intro p + p { margin-top: 16px; overflow-wrap: anywhere; }
.project__status { font-size: 12px; font-weight: 700; color: #555; margin: 0 0 18px; }
.project__evidence { margin: 0 0 24px; font-size: 14px; line-height: 1.7; }
.project__evidence > div + div { margin-top: 14px; }
.project__evidence dt { font-weight: 700; color: #1a1a1a; margin-bottom: 3px; }
.project__evidence dd { margin: 0; color: #5f5f5f; }
/* Case-study paragraphs need more reading width than the template's image tiles. */
.tokyo_tm_portfolio .portfolio_list { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 32px; margin: 0; }
.tokyo_tm_portfolio .portfolio_list > li { float: none; width: auto; margin: 0; padding: 0; }
body.dark .hero-role, body.dark .project__evidence dt { color: #fff; }
body.dark .content-link, body.dark .hero-actions a { color: #a8d2ff; }
body.dark .content-band { background: #303136; }
body.dark .content-band, body.dark .section-intro, body.dark .contact-intro, body.dark .hero-location { color: #ccc; }
body.dark .content-band--plain { background: transparent; }
body.dark .capability-grid article { border-color: #686a70; }
body.dark .delivery-panel, body.dark .delivery-panel__cta { border-color: #686a70; }
body.dark .delivery-panel__lead, body.dark .delivery-panel__list p, body.dark .delivery-panel__number, body.dark .delivery-panel__note { color: #ccc; }
body.dark .capability-tools, body.dark .project__status, body.dark .project__evidence dd { color: #ccc; }
@media (max-width: 767px) {
	.content-columns, .capability-grid { grid-template-columns: 1fr; gap: 30px; }
	.delivery-panel__list { grid-template-columns: 1fr; }
	.delivery-panel { padding: 24px; }
	.tokyo_tm_portfolio .portfolio_list { grid-template-columns: minmax(0, 1fr); }
	.content-band { padding: 45px 0; }
	.hero-actions { justify-content: center; }
}

/* Progressive layout: every section remains readable without JavaScript.
   navigation.js hides inactive sections only after successful initialization. */
html { scroll-padding-top: 70px; }
body::-webkit-scrollbar { width: auto; }
.tokyo_tm_section,
.tokyo_tm_section.active,
.tokyo_tm_section.animated,
.tokyo_tm_section.hidden {
	position: relative;
	inset: auto;
	overflow: visible;
	opacity: 1;
	visibility: visible;
	transition: none;
	animation: none;
	display: flow-root;
}
.tokyo_tm_all_wrap .rightpart_in { display: flow-root; }
.tokyo_tm_home { min-height: 100svh; padding: 60px 0; }
.tokyo_tm_home .avatar .image {
	width: 100%;
	height: 100%;
	object-fit: cover;
	object-position: center;
	animation: none;
}
/* Restore the original contour animation with a native, script-independent
   pause control. The portrait's dimensions stay fixed throughout the motion. */
.portrait {
	display: grid;
	width: 300px;
	grid-template-columns: minmax(0, 1fr) 18px auto minmax(0, 1fr);
	gap: 8px;
	align-items: center;
	flex-shrink: 0;
}
.tokyo_tm_home .portrait .avatar {
	grid-area: 1 / 1 / 2 / -1;
	width: 100%;
	min-width: 0;
	min-height: 0;
	aspect-ratio: 1;
	margin-bottom: 0;
}
.portrait-pause {
	grid-area: 2 / 2;
	appearance: auto;
	width: 18px;
	height: 18px;
	margin: 0;
	padding: 0;
	accent-color: #333;
	cursor: pointer;
}
.portrait-pause-label {
	grid-area: 2 / 3;
	display: inline-flex;
	align-items: center;
	min-height: 44px;
	font-size: 12px;
	line-height: 1.5;
	color: #5f5f5f;
	cursor: pointer;
}
.tokyo_tm_home .avatar[data-type="wave"] .image {
	border-radius: 60% 40% 30% 70% / 60% 30% 70% 40%;
	animation: morph 8s ease-in-out infinite 1s;
}
.portrait-pause:checked ~ .avatar .image { animation-play-state: paused; }
body.dark .portrait-pause { accent-color: #ddd; }
body.dark .portrait-pause-label { color: #ccc; }
@media (max-width: 1600px) {
	.portrait { width: 250px; }
}
@media (max-width: 1040px) {
	.portrait { margin-bottom: 24px; }
}
@media (max-width: 768px) {
	.portrait { width: 200px; }
}
@media (prefers-reduced-motion: reduce) {
	.tokyo_tm_home .avatar[data-type="wave"] .image { animation: none !important; border-radius: 50%; }
	.portrait-pause, .portrait-pause-label { display: none; }
}
.tokyo_tm_home .social ul li a { min-width: 44px; min-height: 44px; }
.theme-toggle { left: 70px; right: auto; min-height: 44px; line-height: 20px; }
.fallback-nav, .tokyo_tm_topbar { display: none; }
.tokyo_tm_portfolio .portfolio_list > li,
.tokyo_tm_portfolio .project { min-width: 0; }
.tokyo_tm_read_more a { min-height: 44px; display: inline-flex; align-items: center; }
.tokyo_tm_read_more a span { transform: none !important; }
.tokyo_tm_read_more a::before { display: none; }
.tokyo_tm_news .image { aspect-ratio: 8 / 5; }
.tokyo_tm_news ul li .details .date span::before { content: none; }
.project__sources {
	margin: 0 0 22px;
	padding: 0;
	list-style: none;
	display: flex;
	flex-wrap: wrap;
	gap: 4px 16px;
}
.project__sources a { display: inline-flex; align-items: center; min-height: 44px; font-size: 13px; text-decoration: underline; text-underline-offset: 3px; }
.contact-email { clear: both; padding-top: 8px; overflow-wrap: anywhere; }
.contact-email h3 { font-size: 22px; margin-bottom: 16px; }
.contact-email p { max-width: 65ch; margin-bottom: 24px; }
.map-link { clear: both; margin-bottom: 32px; }
.tokyo_tm_contact .map_wrap:has(.map-facade[hidden]) { display: none; }
.tokyo_tm_modalbox, .tokyo_tm_modalbox .box_inner { transition: opacity .15s ease; }
.hamburger { border: 0; background: transparent; color: #1a1a1a; }
.tokyo_tm_topbar .hamburger { width: 44px; height: 44px; padding: 10px; display: flex; align-items: center; justify-content: center; }
.tokyo_tm_topbar .trigger { top: 0; }
.tokyo_tm_mobile_menu .menu_list { padding: 0; }
.menu-icon, .menu-icon::before, .menu-icon::after { display: block; width: 24px; height: 2px; background: currentColor; position: relative; }
.menu-icon::before, .menu-icon::after { content: ""; position: absolute; left: 0; }
.menu-icon::before { top: -7px; }
.menu-icon::after { top: 7px; }
.hamburger[aria-expanded="true"] .menu-icon { background: transparent; }
.hamburger[aria-expanded="true"] .menu-icon::before { top: 0; transform: rotate(45deg); }
.hamburger[aria-expanded="true"] .menu-icon::after { top: 0; transform: rotate(-45deg); }
body.dark .hamburger { color: #fff; }
body.dark .project__sources a { color: #a8d2ff; }
.tokyo_tm_info ul li span:last-child,
.tokyo_tm_info ul li span:last-child a,
.contact-email p { color: #666; }
body.dark .tokyo_tm_info ul li span:last-child,
body.dark .tokyo_tm_info ul li span:last-child a,
body.dark .contact-email p { color: #ccc; }
body.dark .writeup-tags > li { color: #ccc; }
html:not(.has-dialogs) .tokyo_tm_services .service_hidden_details {
	display: block;
	position: static;
	opacity: 1;
	visibility: visible;
	z-index: auto;
	margin-top: 24px;
}
html:not(.has-dialogs) .tokyo_tm_services .service_hidden_details p + p { margin-top: 16px; }
html:not(.has-dialogs) .tokyo_tm_services .tokyo_tm_full_link,
html:not(.has-dialogs) .tokyo_tm_services .tokyo_tm_read_more { display: none; }

@media (max-width: 1200px) {
	.tokyo_tm_topbar { display: none; }
	html.is-enhanced .tokyo_tm_topbar { display: block; }
	.theme-toggle { left: 20px; top: 3px; bottom: auto; padding: 8px 14px; box-shadow: none; }
	.tokyo_tm_topbar .topbar_inner { height: 50px; }
	.tokyo_tm_mobile_menu { top: 50px; right: 0; width: 100%; height: calc(100dvh - 50px); padding: 24px; overflow-y: auto; }
	.tokyo_tm_mobile_menu .menu_list ul li a { display: flex; align-items: center; min-height: 44px; }
	html:not(.is-enhanced) .fallback-nav { display: flex; flex-wrap: wrap; gap: 8px 24px; padding: 16px 20px; border-bottom: 1px solid #d6d6d6; }
	.fallback-nav a { display: inline-flex; align-items: center; min-height: 44px; text-decoration: underline; color: inherit; }
	.tokyo_tm_home { padding: 90px 0 50px; }
	html:not(.is-enhanced) .tokyo_tm_home { padding-top: 40px; }
	/* Accommodate an optional template wordmark without covering either control. */
	body:has(.tokyo_tm_topbar .logo) .theme-toggle { left: auto; right: 74px; }
	.tokyo_tm_topbar .logo h3 { font-size: 18px; letter-spacing: 2px; }
}

/* Python portfolio content and the contact workflow share the existing palette. */
.course-links { margin-top: 18px; }
.course-links a { overflow-wrap: anywhere; }
.featured-research { margin-bottom: 64px; padding: 32px; border: 1px solid #c8c8c8; }
.featured-research h2 { font-size: clamp(24px, 3vw, 34px); line-height: 1.3; margin-bottom: 22px; overflow-wrap: anywhere; }
.featured-research p { max-width: 72ch; color: #555; margin-bottom: 22px; }
.research-summary { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 24px; margin: 28px 0; }
.research-summary dt { font-weight: 700; color: #222; }
.research-summary dd { margin: 8px 0 0; color: #555; }
.research-links { display: flex; flex-wrap: wrap; gap: 12px 28px; }
.research-links a { display: inline-flex; align-items: center; min-height: 44px; }
.research-live { margin-top: 28px; border-top: 1px solid #888; padding-top: 24px; }
.research-live h3 { font-size: 18px; line-height: 1.5; margin-bottom: 18px; }
.research-stats { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; margin-bottom: 20px; }
.research-stats dt { font-size: 14px; color: #555; }
.research-stats dd { margin: 6px 0 0; color: #222; font-size: 26px; font-weight: 700; }
#research-status { font-size: 14px; }
#research-updated { display: block; margin-bottom: 20px; color: #555; font-size: 13px; }
.research-refresh { min-height: 44px; padding: 10px 16px; border: 1px solid #555; background: transparent; color: #333; font: inherit; font-size: 14px; font-weight: 600; cursor: pointer; }
.research-refresh:disabled { cursor: wait; opacity: .75; }
body.dark .research-stats dt, body.dark #research-updated { color: #ccc; }
body.dark .research-stats dd, body.dark .research-refresh { color: #eee; }
body.dark .research-refresh { border-color: #aaa; }
.contact-form-panel { clear: both; margin-bottom: 56px; }
.contact-form-panel h3, .contact-location-title { margin-bottom: 18px; font-size: 22px; }
#contact-privacy { color: #555; max-width: 65ch; margin-bottom: 28px; }
.contact-fields { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 24px; }
.contact-field { min-width: 0; }
.contact-field--wide { grid-column: 1 / -1; }
.contact_form input:not([type="hidden"]), .contact_form textarea {
	display: block; width: 100%; min-width: 0; min-height: 48px; padding: 12px 14px;
	border: 1px solid #777; border-radius: 2px; background: #fff; color: #222;
	font: inherit; font-size: 16px; line-height: 1.5; box-shadow: none;
}
.contact_form textarea { height: auto; resize: vertical; }
.contact_form [aria-invalid="true"] { border: 2px solid #a42020; }
.contact-field-error { display: block; color: #a42020; font-size: 14px; line-height: 1.5; margin-top: 8px; }
.contact-submit, .map-retry {
	display: inline-flex; justify-content: center; align-items: center; min-height: 48px;
	padding: 12px 24px; border: 1px solid #222; border-radius: 2px;
	background: #222; color: #fff; font: inherit; font-weight: 700; cursor: pointer;
}
.contact-submit { margin-top: 28px; }
.contact-submit:hover, .map-retry:hover { background: #444; }
.contact-submit:disabled { cursor: wait; opacity: .75; }
#contact-status { min-height: 1.7em; margin: 16px 0; color: #444; overflow-wrap: anywhere; }
#contact-status[data-state="error"] { color: #a42020; }
#contact-status[data-state="success"] { color: #236332; }
.contact-fallback { color: #555; overflow-wrap: anywhere; }
.map-status { clear: both; min-height: 1.7em; color: #555; margin: 16px 0; }
.map-actions { clear: both; display: flex; align-items: center; flex-wrap: wrap; gap: 16px 24px; margin-bottom: 32px; }
.map-actions .map-link { margin: 0; }
body.dark .featured-research { border-color: #777; }
body.dark .featured-research p, body.dark .research-summary dd, body.dark #contact-privacy,
body.dark .contact-fallback, body.dark .map-status, body.dark #contact-status { color: #ccc; }
body.dark .research-summary dt { color: #fff; }
body.dark .contact_form input:not([type="hidden"]), body.dark .contact_form textarea { background: #303136; color: #fff; border-color: #999; }
body.dark .contact_form [aria-invalid="true"] { border-color: #ffaaaa; }
body.dark .contact-field-error, body.dark #contact-status[data-state="error"] { color: #ffaaaa; }
body.dark #contact-status[data-state="success"] { color: #a6dfb4; }
body.dark .contact-submit, body.dark .map-retry { background: #eee; color: #222; border-color: #eee; }
body.dark .contact-submit:hover, body.dark .map-retry:hover { background: #d6d6d6; }
@media (max-width: 600px) {
	.contact-fields, .research-summary { grid-template-columns: minmax(0, 1fr); }
	.featured-research { padding: 22px; }
	.contact-submit { width: 100%; }
}

/* Writing archive: native controls, full article titles and one cover system. */
.writeups-controls {
	display: flex;
	align-items: flex-end;
	flex-wrap: wrap;
	gap: 20px 24px;
	margin: 32px 0 20px;
}
.writeups-search-field { flex: 1 1 280px; min-width: 0; }
.writeups-search-field label { display: block; margin-bottom: 8px; color: #222; font-size: 15px; font-weight: 600; }
.writeups-search-row { display: flex; gap: 8px; }
.writeups-controls input[type="search"] {
	width: 100%; min-width: 0; min-height: 48px; padding: 10px 12px;
	border: 1px solid #777; border-radius: 2px; background: #fff; color: #222;
	font: inherit; font-size: 16px; line-height: 1.5; box-shadow: none;
}
.writeups-controls input::placeholder { color: #666; opacity: 1; }
.writeups-controls button {
	min-height: 48px; flex-shrink: 0; padding: 10px 16px; border: 1px solid #777;
	border-radius: 2px; background: transparent; color: #222; font: inherit; font-size: 14px;
	font-weight: 600; line-height: 1.5; cursor: pointer;
}
.writeups-controls button:hover:not(:disabled):not([aria-disabled="true"]) { background: #e9e9e9; }
.writeups-controls button:disabled, .writeups-controls button[aria-disabled="true"] { opacity: .55; cursor: default; }
.writeups-auto { display: flex; align-items: center; gap: 10px; min-height: 48px; color: #222; font-size: 14px; cursor: pointer; }
.writeups-auto input { width: 18px; height: 18px; flex: none; margin: 0; accent-color: #222; cursor: pointer; }
.writeups-meta { display: flex; align-items: baseline; justify-content: space-between; flex-wrap: wrap; gap: 8px 24px; }
.writeups-meta p { margin: 0; color: #222; font-size: 14px; font-weight: 600; }
.writeups-meta time, .writeups-status, .writeups-profile { color: #555; font-size: 14px; line-height: 1.7; }
.writeups-status { min-height: 1.7em; margin: 8px 0 28px; }
.writeups-status:empty { display: none; }
.writeups-profile { clear: both; margin: 32px 0 0; }
.writeups-empty { padding: 28px 0; margin: 0; color: #555; }
.tokyo_tm_news > .writeups-list {
	display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 36px 32px; margin: 0; padding: 0; width: 100%; clear: both;
}
.tokyo_tm_news > .writeups-list > li { width: auto; min-width: 0; margin: 0; padding: 0; float: none; }
.tokyo_tm_news > .writeups-list > li > .list_inner { display: flex; flex-direction: column; float: none; box-shadow: none; }
.tokyo_tm_news .writeups-list .image { flex: none; width: 100%; min-width: 0; max-width: 100%; aspect-ratio: 16 / 9; min-height: 210px; }
.tokyo_tm_news .writeups-list .image .main { transition: none; }
.tokyo_tm_news .writeups-list > li .list_inner .image:hover .main { transform: none; }
.tokyo_tm_news .writeups-list .details {
	flex: 1; display: flex; flex-direction: column; float: none; padding: 26px 28px;
	border: 1px solid #dedede; border-top: 0;
}
.tokyo_tm_news .writeups-list .details .extra { margin-bottom: 16px; }
.tokyo_tm_news .writeups-list .details .extra::before { display: none; }
.tokyo_tm_news .writeups-list .details .date { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 14px; font-size: 12px; line-height: 1.6; }
.tokyo_tm_news .writeups-list .details .date span { position: static; margin: 0; padding: 0; }
.tokyo_tm_news .writeups-list .details .date time { white-space: nowrap; }
.tokyo_tm_news .writeups-list .details .title { margin-bottom: 12px; }
.tokyo_tm_news .writeups-list .details .title a { font-size: 18px; line-height: 1.55; overflow-wrap: anywhere; }
.tokyo_tm_news .writeups-list .writeup-excerpt { margin: 0; font-size: 14px; line-height: 1.75; }
.tokyo_tm_news .writeups-list .writeup-tags { margin-top: 18px; gap: 8px; }
.tokyo_tm_news .writeups-list .writeup-tags li { max-width: 100%; white-space: normal; overflow-wrap: anywhere; }
.tokyo_tm_news .writeups-list .tokyo_tm_read_more { margin-top: auto; padding-top: 18px; }
body.dark .writeups-search-field label, body.dark .writeups-meta p { color: #eee; }
body.dark .writeups-controls input[type="search"] { background: #303136; color: #fff; border-color: #999; }
body.dark .writeups-auto { color: #eee; }
body.dark .writeups-auto input { accent-color: #eee; }
body.dark .writeups-controls input::placeholder { color: #bbb; }
body.dark .writeups-controls button { color: #eee; border-color: #999; }
body.dark .writeups-controls button:hover:not(:disabled):not([aria-disabled="true"]) { background: #393a3e; }
body.dark .writeups-meta time, body.dark .writeups-status, body.dark .writeups-profile, body.dark .writeups-empty { color: #ccc; }
body.dark .tokyo_tm_news .writeups-list .details { border-color: #555; }
@media (max-width: 767px) {
	.tokyo_tm_news > .writeups-list { grid-template-columns: minmax(0, 1fr); gap: 32px; }
	.tokyo_tm_news .writeups-list .image { min-height: 190px; }
	.tokyo_tm_news .writeups-list .details { padding: 24px; }
	.writeups-controls { gap: 12px; margin-top: 24px; }
	.writeups-search-field { flex-basis: 100%; }
	.writeups-controls > button { width: 100%; }
	.writeups-controls button { padding-inline: 12px; }
	.writeups-status { margin-bottom: 24px; }
}

/* Keep the document preview clear of the theme's floated download button. */
.resume-preview-link {
	clear: both;
	padding-top: 14px;
	font-size: 14px;
	line-height: 1.65;
}
