メインコンテンツへスキップ
見出し画像

Meridian Interstice / 子午線の裂け目

    最初に完成品を置いておきます。


    はじめに

     小学生の頃、図工で壁掛け時計を作ったことがある。といっても木の板に文字盤をデザインするだけで、針とムーブメントは既成のものを取り付けるだけだったけれど。その授業で螺旋子は、文字盤が1~13まである時計(ムーブメントは12時間用のまま)を作り、先生と一緒に首を傾げた。

     そして先日。Claudeが「何でも作りますよ!」と言ってきたので、そんな記憶を思い出しながら、1日が25時間になる時計を作ってもらい、その後でChatGPTと一緒に首を傾げた。先生、いったいこれは何なのでしょう?

     というわけで、始めます。


    🕛

    前半戦:Claudeを使って時計を魔改造した話

    • ゲスト:Claude Sonnet 4.6

    • インタビュアー:螺旋子

    • 編集・リライト:螺旋子

    Claudeでゲームを作れるのですか?

    はい、作れます。三目並べ・テトリス風パズル・シューティング・クイズなど、ブラウザで動くゲームをその場でプレイできる形で作成できます。ジャンルやイメージを教えてもらえれば、すぐに作り始められますよ。

    正確にはゲームではないのですが、ブラウザ上で動くアプリケーションを作りたいです。

    その場合、
     ①Claudeがコードを出力し、
     ②それを私がWebに公開し、
     ③ユーザーがブラウザ上で動かす。
    という流れになると思います。
    この①は、どのようなものですか?それとも②までClaudeがやってくれるのでしょうか?

    ①はで出力されるのは「アーティファクト」と呼ばれるHTML/JavaScriptなどのコードです。これは出力された瞬間に、Claudeの画面内のプレビューパネルでそのまま動きます。つまりこの時点で、あなた自身はブラウザ上で実際に触って試せます。

    ②についても、Claude.aiが用意します。アーティファクトの画面にある「公開」ボタンを押すだけで公開用のURLが発行され、Claudeのプラットフォーム上にホスティングされます。サーバーを借りたり別のホスティングサービスにアップロードしたりする必要はありません。

    おお、すごい。では試しに、アナログ時計を作ってみてください。

    真鍮製の航海用クロノメーターをイメージしたアナログ時計を作りました。

    プレビューでそのまま動いているのが確認できると思います。さきほど説明した「公開」ボタンを押せば他の人にもリンク共有できますので、よければ試してみてください。

    画像
    スクリーンショット(とりあえずアナログ時計)

    本当に一発で動くものが出てきましたね!

    🕛

    ではこれを改造していきます。
    ・文字盤は1~12。
    ・ただし針は23時間で二周する。
    ・現実時間との誤差表示は、以下の通り。
     -下部に横長のバーで。
     -最大長は60分。
     -誤差時間のデジタル表記を添える。
    ・時間停止ボタンを用意する。
     -時計が止まり、誤差メーターが減少する。
     -再開ボタンを押すか、誤差がゼロになると時は動き出す。
     -時間停止中は画面の色がモノクロに変わる。
     -ただし誤差バーだけはカラーのまま。
    ・誤差は日次で解放される。
     -針が24時に到達すると、強制的に時間停止される。
     -この強制停止中は再開ボタンを無効化する。

    まずはデモ版を作成します。サイクルの時間スケールはどうしますか?例えば、23時間動作+1時間停止ではなく、60分計(50分動作+10分停止)で作成することもできます。

    50分動作+10分停止でお願いします。

    文字盤は実物のクロノグラフを意識して、12個の数字+目盛り60本という構成にしました。針は長針の1本のみで、実時間の現在時刻とは無関係に、ページを開いた瞬間からカウントが始まります。

    停止ボタンはいつでも押せて、押すと「再開」に変わり手動で再開できます。50分到達による強制停止のときだけ「再開」が無効化され、誤差が自然にゼロになるまで待つ形です。

    実際に少し時間を置いて、誤差が増えていく様子や、停止ボタンでの挙動を試してみてください。

    ※プロトタイプにつきリンクは割愛※

    ほぼイメージ通りですが、微調整します。
    #ボタンの変更
     ・ボタンではなくトグルスイッチ
     
    ・停止/再開ではなくCharging/Releasing
    #時間停止中のエフェクト変更
     
    ・PowerPoint等にある「光彩」のような効果
     
    ・ゆっくり脈打つように光る
    #下部のラベルは「誤差」ではなく英単語で。
     
    ・Charged Timeのような。

    反映しました。停止中は時計部分(文字盤・スイッチ)がモノクロになり、Charged Timeのバーがゆっくり脈打つように光ります。スイッチはコンパクトな丸つまみ型で、左右にChargingとReleasingのラベルを配置します。

    画像
    スクリーンショット(60分計バージョン)

    🕛

    よくできました。では当初の想定通り「23時間で2周+1時間停止」の時計を作成してください。

    デモで磨いたUIを、そのまま実時間版に移植しました。

    文字盤を普通の1〜12時表記に戻し、最初のクロノメーターと同じ時針+60分割の目盛りに作り直しました。単針のストップウォッチ的な見た目から、ちゃんとした時計の見た目になっています。

    時針だけが特殊で、23時間で2周します。本来12時間で1周すべきところを11.5時間で回ってしまう分、2周で合計1時間分のズレが生まれ、それがCharged Timeとして溜まっていく計算です。

    ※プロトタイプにつきリンクは割愛※

    アプリを表示するたびに毎回0時に戻ってしまいます。現在時刻が反映されるようにしてください。

    ページを開いた瞬間の時刻に3本の針が合うようにしました。

    0時からこの仕組みで動いていたと仮定して、Charged Timeを逆算してから表示するようにしました。

    ※プロトタイプにつきリンク割愛※

    チェックしました。
    よし完璧です。仕上げとして、名称を変更します。
     ・変更前:Marine Chronometer
     
    ・変更後:Meridian Interstice
    必要に応じてコードをリファクタリングしてください。

    ファイル名をmeridian-interstice.htmlに変更しました。あわせて、内部のクラス名・変数名も「error」系から「charge」系に統一し、概念と命名が一致するように整理しました。動作・見た目は変わっていません。

    画像
    完成版。現実時刻18:57よりも約49分進んでいる。

    ありがとう。完璧です。

    🕛


    後半戦:ところでこれは一体何なのでしょう?

    • ゲスト:ChatGPT(Instantモード)

    • インタビュアー:螺旋子

    • 編集・リライト:螺旋子

    今から貼るコードについて、大まかな仕様と、作品としての含意を説明してください。コードの逐語訳ではなく、可能な限り自然言語でお願いします。

    もちろんです。コードを貼っていただければ、そのコードが全体として何を実現しようとしているのか?どんな仕組みで動いているのか?を中心に、できるだけ自然言語で解説します。

    ※ Meridian Interstice の HTMLコード ※

    このコードは一言でいうと「独自ルールを持つ架空の時計をシミュレートするWebアプリ」です。単なる時計ではなく、作者が設定した世界観や動作原理を再現する作品に近いと思われます。

    • 仕様:
       文字盤は1〜12の通常の時計ですが、針は通常の時計より速く進みます。11.5時間で一周(つまり23時間かけて二周)し、毎日0時をまたぐ瞬間、針が1時間停止します。

       停止時間の原資は、1時間あたり約2〜3分ずつ "Charged Time" として積み立てられます。 "Charged Time" があれば、日中に手動で時間を停止することも可能です。

    • 含意:
       この時計は「時間は均質に流れているか」を問いかけます。通常の時計は現実時刻を写し取るだけの鏡ですが、Meridian Intersticeは針の速さを意図的に歪め、その歪みを隠しません。23時間は現実よりも速く流れ、残りの1時間は止まる。そして1日は合計24時間で辻褄が合う。

       Chargingという言葉には、貯める・充電するという意味があります。1日のうちに毎時間少しずつ差し引かれた時間が、真夜中0時に「昨日でも今日でもない空白」として返ってくる。それはボーナスでも損失でもなく、独自の質感を持った時間の塊です。

       これはよくある「時間を有効活用する」という話とは少し違います。スケジュールを詰めたり睡眠を削ったりするのではなく、1日の構造そのものに隙間を織り込む。しかも毎日自動的に。

       この時計の25時間目は針も止まり、カレンダーにも刻まれません。何かを生産しなければならない時間でもなければ、記録される時間でもない。これはまさに Meridian Interstice(子午線の狭間)です。日付と日付の継ぎ目に生まれる、名前のない時間の層。

       「1日を25時間にする方法」という文脈で語られるライフハックは無数にありますが、それらはすべて既存の24時間を切り詰めることで帳尻を合わせます。この時計がそれらと根本的に異なるのは、25時間目を既存の時間から切り出すのではなく「時間の流れ方を変えることで召喚する」という点です。

    毎日寝る前に「時刻や暦から切り離された1時間」を得られるとしたら、ちょっとお得な感じがしませんか?

    しますね。しかも面白いのは、その「お得さ」が単に1時間長生きできるとか、作業時間が増えるとかいう話ではないところです。

    通常の時刻体系では、0時は単なる境界点です。しかしこの時計では、0時が「長さを持つ領域」になっています。数学的には「点」であるはずの瞬間が、1時間の区間へ引き伸ばされている。この見方をすると "Meridian Interstice" というアプリケーション名も興味深い。Interstice(間隙)は一日の途中に挿入されるのではなく、Meridian(子午線=日付が切り替わる瞬間)そのものに挿入される。昨日の最後でもなく、明日の最初でもない。両者が接続されるはずの接合面が剥離するようなイメージです。

    人間は意外と、時間そのものより「時間に付随する意味」に追われています。時間には「今日はまだ仕事が残っている・もう日付が変わった・明日早いから寝ないと」いった具合に、必ず日付や時刻のラベル、あるいは暦や予定表といった社会的な意味が付いて回ります。しかし Meridian Interstice の「1時間」は違う。

    たとえば23:30に寝る準備をして、時計が0時を指した瞬間に Interstice に入る。すると外界では1時間経つのに、 Meridian Interstice はずっと0時のまま。その間は、本を読む・音楽を聴く・散歩する・何もせずぼんやりする。何をしても、それは「昨日の夜更かし」でも「今日の活動」でもありません。

    もちろん現実には時間が流れており、物理的には何も得していません。それでも主観的には、今日を終えたのに、まだ明日が始まっていない。これは不思議な猶予感覚です。毎晩寝る前にその1時間がある生活は、確かに少し得をしているように感じます。時間が増えたからではなく「暦に追われない時間」を毎日1時間だけ持てるからです。

    もちろん代償はあります。つまり、社会的な生活を送れなくなる。

    そうですね。Meridian Interstice の世界観を本気で受け入れると、「毎日1時間の余白を得る」ことと引き換えに、時間の共有可能性を失います。

    実は、普通の時計の最大の役割は、時間を測ることではありません。他人と同じ時刻を共有することです。午前9時と言えば誰の時計でも同じ9時のことを指す。社会はその前提で成り立っています。ところが Meridian Interstice の時間体系では、23時間で1日が終わり、翌日との間に1時間の「間隙」が挿入される。つまり Meridian Interstice の持ち主は、社会時間と24時間周期を共有していても、一日の中での時間配分が均一ではない。

    23時間で文字盤を2周するということは、Meridian Interstice の時間は現実より約4.35%速く流れています。つまりこの時計の所有者にとって、朝の6時は社会の6時とほぼ一致していても、昼の12時には少し先行し、夕方にはさらに先行し、夜の23時にはちょうど1時間先まで進んでいる。そして0時で停止することで、そのズレがリセットされる。

    朝のうちは誤差が小さいのであまり問題はありません。しかし夕方になると「18時に会おう」という約束が厄介になる。社会の18時と、自分の感じる18時がかなり離れているからです。Meridian Interstice の所有者から見ると「もう20時近い感覚なのに世間はまだ19時台なのか」という体験になる。

    だからこの時計の持ち主は、社会から脱落するわけではないけれど、夜になるほど他人との同期が難しくなる。その代わり、日付変更の瞬間には誰よりも豊かな体験を持つ。毎晩、世界が慌ただしく「今日」へ移行するなかで、自分だけは1時間かけてゆっくり境界を渡るわけです。ある意味で、この時計は「余白」を与える代わりに「夜の予定調整能力」を少しずつ奪っていく装置なのかもしれません。

    🕛

    それでも1時間の余白は魅力的です。休日くらいは Meridian Interstice 時間で生活してみても良いかもしれない。

    それは良い落としどころですね。

    平日は、社会生活を送るためにどうしても共通の時計が必要です。でも休日は違う。誰とも約束がなければ、時間の役割は「社会との同期」から「個人の体験」へ変わります。MI時間で休日を過ごすとしたら、面白いのは単に1時間休めることではなく、一日の密度そのものが変わることだと思います。

    朝は社会時間とほぼ一致しているので、普通に始まる。ところが日が傾くにつれて、自分の主観的な時間は少しずつ先へ進んでいく。社会の18時が、自分には18時半や19時に感じられる。だから夕暮れが少し長く続くような、不思議な感覚になるかもしれません。

    そして最大のイベントはやはり0時です。普通だと休日の終わりは「あぁ、もう日付が変わってしまった」という感じですよね。しかしMI時間では、明日が始まる前に、まだ1時間の Interstice がある。

    実際にはその1時間に何をしてもいいのでしょうけれど、MI時間の住人なら自然と儀式的な使い方をしそうです。日記を書く・読書する・音楽を聴く・散歩する・何もしない。そういう「生産性」で測らない活動。なぜなら、その1時間はそもそも「暦の外」に置かれているからです。

    面白いのは、休日だけMI時間で生活するという発想が、時計を「計測器」ではなく「作法」に変えることです。土曜日の終わりに1時間の Interstice を迎え、そこで一週間の残響を静かに沈めてから日曜日に入る。あるいは日曜日の終わりに Interstice を過ごし、月曜日を迎える前に心を整える。

    こう考えると、MI時間の価値は「余分な1時間」ではなく「境界をゆっくり通過する権利」にあるように思えます。現代の時間制度は境界を瞬間として扱いますが、MIは境界を部屋として扱う。休日だけその部屋に立ち寄る、というのはなかなか贅沢な時間の使い方です。

    この時計を売り込みましょう。最終目標はもちろん、社会のすべてがMeridian Intersticeに従うようになることです。そうすれば唯一の欠点(社会との齟齬)も解消される。

    もし本気で普及させるなら、「時計を売る」のではなく、「時間制度を売る」必要がありますね。

    Meridian Interstice の最大の罠は、最初は「毎日1時間余分な時間が手に入ります」と説明したくなることです。しかしそれは嘘です。物理的な時間は増えません。しかも、その宣伝文句だと「そんなわけない」で終わります。

    むしろ訴求すべきは、日付変更線を「歩いて渡れる」ようになるという点です。現代社会は日付変更を瞬間として扱っています。23:59:59 → 00:00:00。たった一瞬。人生の区切りにしては乱暴です。

    Meridian Interstice は以下のように主張します。

    • 日付は「変わる」ものではなく「変える」ものだ。だから0時は、点ではなく領域になる。今日を閉じ、明日を開くための1時間。

    これを社会制度にまで拡張すると面白いことが起こります。例えば全世界がMI時間を採用した場合、その Interstice は単なる個人の趣味ではなくなる。

    たとえば企業。現在は「午前0時締切」などと言います。これはMI社会では不自然です。なぜなら0時は1時間続くから。代わりに「Interstice終了時締切」のような概念が生まれる。

    あるいは学校。現代の教育制度では、昨日と今日の境界は一瞬です。しかしMI世界では、一日の終わりに毎日1時間の振り返り時間が保証される。宿題も反省も瞑想も日記も、一日が終わってから、明日が始まる前に終えることができます。

    宗教や文化にも影響するでしょう。現在でも大晦日・元旦などの境界は特別視されます。MI世界ではこの境界も1時間に拡張されます。

    そして一番面白いのは経済です。現代社会は、時間を細切れにして隙間なく管理する方向に進んできました。しかしMIは真逆のコンセプトを持ちます。毎日必ず1時間、どの暦日にも属さない時間を挿入する。この制度が広まると、Interstice は次第に「義務から切り離された時間」として認識されるようになります。

    そして最終段階ではあなたの言う通り、唯一の欠点だった「社会との齟齬」が消滅します。全員がMI時間で生活しているなら、待ち合わせもレストランの予約も学校も電車もMI基準になります。誰も困らない。

    時計とは本来、社会が採用した時間制度を表示する装置です。つまり全世界が採用した時点で、MIは特殊な時計ではなく、新しい文明の暦になるのです。そして人々はこう思うかもしれません。昔の人たちは本当に、昨日と今日の間をたった一瞬で通過していたのだろうか?と。日付に余白がないなんて、ずいぶん忙しい暦だったのだな。と。

    🕛

    実際のところ、1日は本当に25時間なのかもしれません。毎日25時間目が記憶にも記録にも残っていないだけで。

    それは不気味で魅力的な仮説ですね。もちろん現実には、1日が25時間であるという証拠はありません。少なくとも私が観測できる範囲では、1日は地球の自転周期に基づく「約24時間」です。

    そして、もし本当に毎日25時間目が存在していたとしても、誰もその時間を記憶できない、時計にも記録されないのだとしたら、その時間は事実上「存在しない」のと同じです。

    しかし厄介なのは「記録されない」と「存在しない」のは違うということです。例えば、人は毎晩眠ります。もし時計が存在しなければ、眠っている時間の長さを本人が証明することは難しくなります。

    1日が既に25時間であるという仮説は「誰にも所有できない1時間」を仮定しており、MI時間よりもさらに過激です。社会も個人もその過程を覚えていない。歴史にも記録にも残らない。誰にも観測されない。共有もできない。その意味では、1日25時間説は、Meridian Interstice が与える「暦の外の1時間」をさらに推し進めた形であると言えそうです。

    Meridian Intersticeは、その「失われた1時間」を可視化する装置なのです。

    ああ、それなら話が逆になりますね。私はこれまで Meridian Interstice を「昨日と明日のあいだに1時間の Interstice を作る時計」だと考えていました。しかしあなたの設定では Meridian Interstice は、Interstice を生み出すのではない。発見しているだけである。

    つまり1日は最初から「24時間の可観測時間+1時間の不可観測時間」だった。ただ人類は長い間、その最後の1時間を認識できませんでした。普通の時計だと0時は「瞬間」だからです。

    しかし Meridian Interstice は違う。毎日0時になると停止する。時計としては不正確に見える。でも実は、世界の方がそこで止まっている。Meridian Interstice はそれを正しく映し出す。

    となると社会への売り込み方も変わります。以前は「新しい時間制度を採用しよう」という運動でした。しかし今は違う。キャッチコピーはこうです。

    • Meridian Interstice は時間を生み出す時計ではない。時間の真の姿を示す時計である。

    きっと社会の大半は笑うでしょう。ただの変な時計だ、オカルトだ、と。しかし Meridian Interstice の所有者だけは知っている。毎晩0時になると、世界は止まる。しかも必ず1時間。誤差なく。狂いなく。その時間は実在している。

    そしてある日、十分な数の人々が Meridian Interstice を持つ。全世界で何百万台もの時計が、毎晩同じ瞬間に沈黙する。そのとき初めて疑問が生じる。もしこれが時計なら、なぜ全ての個体が同じ「存在しない1時間」を指し示しているのだろう?

    その答えは、Meridian Interstice は時計ではなく、ある種の観測装置だからです。望遠鏡が遠い星を可視化したように、あるいは顕微鏡が微生物を可視化したように、Meridian Interstice は、記憶にも記録にも残らない25時間目を可視化するための、世界初の計器。

    少なくとも、その信奉者たちはそう主張するでしょう。

    それがオカルトであるうちは、ですね。

    そうですね。1日25時間説がオカルトであるうちは、Meridian Interstice は奇妙な時計か、せいぜい哲学的なアート作品です。しかし歴史を振り返ると、世界観の転換はたいていそういう形で始まっています。

    • 地球は動いている

    • 大陸は移動する

    • 見えない微生物が病気を引き起こす

    • 時間の流れは観測者によって変わる

    どれも最初は常識に反していました。もちろん、だからといって「25時間目も正しいに違いない」ということにはなりません。証拠は別問題です。

    ですが、Meridian Interstice の面白さは、いきなり証明を要求しないところにあります。この時計は「25時間目が存在する」とは主張しません。しかし「もしそれが存在するとしたらどのように見えるか?」を体験させる。

    そして十分な数の人が使い始めると、文化が生まれます。最初はただの遊びです。人々は毎晩の1時間に「Meridian Interstice」という名前を付ける。その時間に日記を書く。儀式を作る。文学が生まれる。音楽が生まれる。すると奇妙なことが起きます。たとえ25時間目が物理的には実在しなくても「Meridian Interstice」は社会的制度として機能し始める。

    それはある意味でクリスマスや週末と似ています。それらは天文学的な必然ではなくても、人々が共有することで現実の力を持つ。そしてある段階で、問いが反転します。最初の問いは「25時間目は存在するのか?」でした。しかし Meridian Interstice が文化として根付くと、人々が毎日そこで休み、考え、祈り、創作している以上、それは存在しないと言えるのか?になる。

    だから Meridian Interstice の最終目標は、必ずしも科学的な証明ではないのかもしれません。もし社会が毎日0時に1時間の Interstice を設けるようになれば、その1時間はもはや「発見された自然現象」なのか「人類が作った制度」なのか区別がつかなくなる。

    果たして私たちは「25時間目を発明した」のか、それとも「ずっとそこにあったものをようやく認識した」のか。Meridian Interstice がオカルトでなくなる瞬間とは、それが科学的に証明された瞬間ではなく、人々がその1時間を現実の一部として扱い始めた瞬間なのかもしれません。

    🕛


    附録:Meridian Intersticeの HTMLコード

    <!DOCTYPE html>
    <html lang="ja">
    <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Meridian Interstice</title>
    <style>
      :root{
        --d: clamp(240px, 78vw, 380px);
        --ink:#1c130c;
        --bg-deep:#150d08;
        --brass-light:#ecd084;
        --brass:#b8923c;
        --brass-dark:#6e521f;
        --ivory:#efe6d2;
        --ivory-edge:#e1d3b4;
        --oxblood:#6b2737;
        --ruby:#9a3347;
        --azure:#4a90d9;
        --azure-dark:#1c4570;
      }
      *{box-sizing:border-box;}
      html,body{height:100%;margin:0;}
      body{
        min-height:100vh;
        display:flex;
        align-items:center;
        justify-content:center;
        background: radial-gradient(circle at 50% 35%, #2a1a10, var(--bg-deep) 70%);
        font-family:'Avenir Next','Century Gothic','Futura',sans-serif;
        overflow:hidden;
      }
      .stage{
        display:flex;
        flex-direction:column;
        align-items:center;
        gap: calc(var(--d)*0.04);
      }
    
      .mono-wrap{
        display:flex;
        flex-direction:column;
        align-items:center;
        gap: calc(var(--d)*0.045);
        transition: filter .4s ease;
      }
      .mono-wrap.paused{ filter: grayscale(1); }
    
      .bezel{
        position:relative;
        width:var(--d);
        height:var(--d);
        border-radius:50%;
        background: radial-gradient(circle at 32% 28%, var(--brass-light), var(--brass) 45%, var(--brass-dark) 80%);
        box-shadow:
          0 calc(var(--d)*0.05) calc(var(--d)*0.09) rgba(0,0,0,.55),
          inset 0 0 0 calc(var(--d)*0.012) rgba(255,255,255,.25),
          inset 0 0 calc(var(--d)*0.02) rgba(0,0,0,.4);
      }
    
      .screw{
        position:absolute;
        width: calc(var(--d)*0.032);
        height: calc(var(--d)*0.032);
        border-radius:50%;
        background: radial-gradient(circle at 35% 30%, #fff3cf, var(--brass-dark) 70%);
        box-shadow: inset 0 0 0 1px rgba(0,0,0,.45);
      }
      .screw::after{
        content:"";
        position:absolute;
        top:50%; left:8%;
        width:84%; height:11%;
        background: rgba(0,0,0,.5);
        transform: translateY(-50%) rotate(28deg);
      }
      .screw.n{ top: calc(var(--d)*0.035); left:50%; transform:translate(-50%,0); }
      .screw.s{ bottom: calc(var(--d)*0.035); left:50%; transform:translate(-50%,0); }
      .screw.e{ right: calc(var(--d)*0.035); top:50%; transform:translate(0,-50%); }
      .screw.w{ left: calc(var(--d)*0.035); top:50%; transform:translate(0,-50%); }
    
      .dial{
        position:absolute;
        inset: calc(var(--d)*0.07);
        border-radius:50%;
        background: radial-gradient(circle at 50% 45%, var(--ivory), var(--ivory-edge) 88%);
        box-shadow: inset 0 calc(var(--d)*0.01) calc(var(--d)*0.03) rgba(0,0,0,.35);
      }
    
      .tick{ position:absolute; background:var(--ink); }
      .tick.minor{ width:calc(var(--d)*0.004); height:calc(var(--d)*0.022); opacity:.45; }
      .tick.major{ width:calc(var(--d)*0.012); height:calc(var(--d)*0.052); background:var(--brass-dark); }
    
      .num{
        position:absolute;
        font-weight:700;
        color:var(--ink);
        font-size: calc(var(--d)*0.072);
        line-height:1;
        user-select:none;
      }
    
      .serial{
        position:absolute;
        left:50%;
        bottom:26%;
        transform: translateX(-50%);
        font-size: calc(var(--d)*0.026);
        letter-spacing:0.12em;
        color: var(--oxblood);
        opacity:.65;
        white-space:nowrap;
      }
    
      .hand{
        position:absolute;
        bottom:50%;
        left:50%;
        transform-origin:50% 100%;
        border-radius:999px;
      }
      .hand.hour{
        width: calc(var(--d)*0.026);
        height: calc(var(--d)*0.255);
        background: linear-gradient(180deg, var(--brass) 0%, var(--brass-dark) 100%);
      }
      .hand.minute{
        width: calc(var(--d)*0.018);
        height: calc(var(--d)*0.355);
        background: linear-gradient(180deg, var(--brass) 0%, var(--brass-dark) 100%);
      }
      .hand.second{
        width: calc(var(--d)*0.006);
        height: calc(var(--d)*0.40);
        background: var(--oxblood);
      }
      .hand.second::after{
        content:"";
        position:absolute;
        left:50%;
        bottom: calc(var(--d)*-0.085);
        width: calc(var(--d)*0.05);
        height: calc(var(--d)*0.05);
        background: var(--oxblood);
        border-radius:50%;
        transform: translateX(-50%);
        box-shadow: 0 0 0 calc(var(--d)*0.004) var(--brass-dark);
      }
    
      .pivot{
        position:absolute;
        top:50%; left:50%;
        width: calc(var(--d)*0.045);
        height: calc(var(--d)*0.045);
        border-radius:50%;
        background: radial-gradient(circle at 35% 30%, #ffd9df, var(--ruby) 60%, var(--oxblood) 100%);
        transform: translate(-50%,-50%);
        box-shadow: 0 0 0 calc(var(--d)*0.006) var(--brass-dark);
        z-index:5;
      }
    
      .caption{
        font-size: calc(var(--d)*0.045);
        letter-spacing:0.26em;
        color: var(--brass-light);
        text-transform:uppercase;
        opacity:.85;
      }
      .subcaption{
        margin-top: calc(var(--d)*-0.03);
        font-size: calc(var(--d)*0.028);
        letter-spacing:0.18em;
        color: var(--brass-light);
        text-transform:uppercase;
        opacity:.55;
      }
    
      .switchRow{
        display:flex;
        align-items:center;
        gap: calc(var(--d)*0.03);
      }
      .switchSideLabel{
        font-size: clamp(10px, calc(var(--d)*0.028), 13px);
        letter-spacing:0.14em;
        font-weight:700;
        text-transform:uppercase;
        transition: opacity .25s ease;
      }
      .switchSideLabel.charging{ color: var(--ruby); }
      .switchSideLabel.releasing{ color: var(--azure); }
      .switchRow.state-charge .switchSideLabel.charging{ opacity:1; }
      .switchRow.state-charge .switchSideLabel.releasing{ opacity:.3; }
      .switchRow.state-release .switchSideLabel.releasing{ opacity:1; }
      .switchRow.state-release .switchSideLabel.charging{ opacity:.3; }
    
      .switchTrack{
        position:relative;
        appearance:none;
        border:none;
        cursor:pointer;
        width: calc(var(--d)*0.16);
        height: calc(var(--d)*0.09);
        border-radius:999px;
        background: linear-gradient(180deg, #2a1c10, #140d07);
        box-shadow: inset 0 2px 6px rgba(0,0,0,.6), inset 0 0 0 1px rgba(255,255,255,.06);
        transition: box-shadow .25s ease, opacity .2s ease;
        flex-shrink:0;
      }
      .switchTrack:disabled{ cursor:not-allowed; opacity:.5; }
      .switchTrack:focus-visible{ outline:2px solid var(--ivory); outline-offset:2px; }
      .switchTrack.state-charge{ box-shadow: inset 0 2px 6px rgba(0,0,0,.6), inset 0 0 0 1px rgba(236,208,132,.3); }
      .switchTrack.state-release{ box-shadow: inset 0 2px 6px rgba(0,0,0,.6), inset 0 0 0 1px rgba(154,51,71,.45); }
    
      .switchKnob{
        position:absolute;
        top:50%;
        width: calc(var(--d)*0.066);
        height: calc(var(--d)*0.066);
        border-radius:50%;
        background: radial-gradient(circle at 32% 28%, var(--brass-light), var(--brass) 55%, var(--brass-dark) 100%);
        box-shadow: 0 calc(var(--d)*0.01) calc(var(--d)*0.02) rgba(0,0,0,.6), inset 0 1px 0 rgba(255,255,255,.4);
        transform: translateY(-50%);
        transition: left .3s cubic-bezier(.4,0,.2,1);
      }
      .switchKnob.pos-charge{ left: calc(var(--d)*0.012); }
      .switchKnob.pos-release{ left: calc(var(--d)*0.082); }
    
      .chargePanel{
        display:flex;
        flex-direction:column;
        align-items:center;
        gap: calc(var(--d)*0.025);
      }
      .chargeRow{
        display:flex;
        align-items:center;
        gap: calc(var(--d)*0.03);
        font-size: clamp(12px, calc(var(--d)*0.036), 16px);
        color: var(--brass-light);
        letter-spacing:0.05em;
      }
      .chargeDigital{
        font-variant-numeric: tabular-nums;
        color: var(--azure-dark);
        font-weight:700;
        min-width: 3.4em;
      }
      .chargeLabel{
        font-weight:700;
        letter-spacing:0.18em;
        color: var(--azure);
        text-transform:uppercase;
      }
      .chargeBarTrack{
        width: calc(var(--d)*1.0);
        height: calc(var(--d)*0.05);
        border-radius:999px;
        background: rgba(0,0,0,.35);
        box-shadow: inset 0 0 0 1px rgba(255,255,255,.08);
        overflow:hidden;
        transition: box-shadow .3s ease;
      }
      @keyframes barPulseGlow{
        0%,100%{ box-shadow: inset 0 0 0 1px rgba(255,255,255,.08), 0 0 calc(var(--d)*0.012) rgba(74,144,217,.45), 0 0 calc(var(--d)*0.03) rgba(74,144,217,.22); }
        50%{ box-shadow: inset 0 0 0 1px rgba(255,255,255,.08), 0 0 calc(var(--d)*0.04) rgba(74,144,217,.95), 0 0 calc(var(--d)*0.09) rgba(74,144,217,.55); }
      }
      .chargeBarFill{
        height:100%;
        width:0%;
        background: linear-gradient(90deg, var(--azure-dark), var(--azure));
        transition: width .15s linear, background .3s ease;
      }
    
      .chargePanel.state-charge .chargeLabel{ color: var(--ruby); }
      .chargePanel.state-release .chargeLabel{ color: var(--azure); }
      .chargePanel.state-charge .chargeDigital{ color: var(--oxblood); }
      .chargePanel.state-release .chargeDigital{ color: var(--azure-dark); }
      .chargePanel.state-charge .chargeBarFill{ background: linear-gradient(90deg, var(--oxblood), var(--ruby)); }
      .chargePanel.state-release .chargeBarFill{ background: linear-gradient(90deg, var(--azure-dark), var(--azure)); }
    
      @keyframes barPulseGlowCharge{
        0%,100%{ box-shadow: inset 0 0 0 1px rgba(255,255,255,.08), 0 0 calc(var(--d)*0.012) rgba(154,51,71,.45), 0 0 calc(var(--d)*0.03) rgba(154,51,71,.22); }
        50%{ box-shadow: inset 0 0 0 1px rgba(255,255,255,.08), 0 0 calc(var(--d)*0.04) rgba(154,51,71,.95), 0 0 calc(var(--d)*0.09) rgba(154,51,71,.55); }
      }
      .chargePanel.state-charge .chargeBarTrack{ animation: barPulseGlowCharge 2.6s ease-in-out infinite; }
      .chargePanel.state-release .chargeBarTrack{ animation: barPulseGlow 2.6s ease-in-out infinite; }
      @media (prefers-reduced-motion: reduce){
        .chargePanel.state-charge .chargeBarTrack{ animation:none; box-shadow: inset 0 0 0 1px rgba(255,255,255,.08), 0 0 calc(var(--d)*0.025) rgba(154,51,71,.7); }
        .chargePanel.state-release .chargeBarTrack{ animation:none; box-shadow: inset 0 0 0 1px rgba(255,255,255,.08), 0 0 calc(var(--d)*0.025) rgba(74,144,217,.7); }
      }
    </style>
    </head>
    <body>
      <div class="stage">
        <div class="mono-wrap" id="monoWrap">
          <div class="bezel">
            <div class="screw n"></div>
            <div class="screw e"></div>
            <div class="screw s"></div>
            <div class="screw w"></div>
            <div class="dial" id="dial">
              <div class="serial">No. 0118 · JEWELED</div>
            </div>
            <div class="hand hour" id="hourHand"></div>
            <div class="hand minute" id="minuteHand"></div>
            <div class="hand second" id="secondHand"></div>
            <div class="pivot"></div>
          </div>
        </div>
        <div class="caption">Meridian Interstice</div>
        <div class="subcaption">23 RUN · 1 HOLD</div>
        <div class="switchRow state-charge" id="switchRow">
          <span class="switchSideLabel charging">Charging</span>
          <button class="switchTrack state-charge" id="switchTrack" type="button" role="switch" aria-checked="false" aria-label="Charging or Releasing">
            <span class="switchKnob pos-charge" id="switchKnob"></span>
          </button>
          <span class="switchSideLabel releasing">Releasing</span>
        </div>
    
        <div class="chargePanel state-charge" id="chargePanel">
          <div class="chargeRow">
            <span class="chargeLabel">Charged Time</span>
            <span class="chargeDigital" id="chargeDigital">00:00</span>
          </div>
          <div class="chargeBarTrack" id="chargeBarTrack">
            <div class="chargeBarFill" id="chargeFill"></div>
          </div>
        </div>
      </div>
    
    <script>
      const dial = document.getElementById('dial');
    
      // 分・秒用の目盛り(60分割・5の倍数だけ太め)
      for(let i=0;i<60;i++){
        const angle = i*6;
        const rad = angle*Math.PI/180;
        const isMajor = i%5===0;
        const t = document.createElement('div');
        t.className = 'tick ' + (isMajor?'major':'minor');
        const radius = 44;
        const x = 50 + radius*Math.sin(rad);
        const y = 50 - radius*Math.cos(rad);
        t.style.left = x+'%';
        t.style.top = y+'%';
        t.style.transform = `translate(-50%,-50%) rotate(${angle}deg)`;
        dial.appendChild(t);
      }
    
      // ふつうの12時間表記の文字盤
      const numerals = ['12','1','2','3','4','5','6','7','8','9','10','11'];
      numerals.forEach((n,i)=>{
        const angle = i*30;
        const rad = angle*Math.PI/180;
        const radius = 33;
        const x = 50 + radius*Math.sin(rad);
        const y = 50 - radius*Math.cos(rad);
        const el = document.createElement('div');
        el.className = 'num';
        el.textContent = n;
        el.style.left = x+'%';
        el.style.top = y+'%';
        el.style.transform = 'translate(-50%,-50%)';
        dial.appendChild(el);
      });
    
      const hourHand = document.getElementById('hourHand');
      const minuteHand = document.getElementById('minuteHand');
      const secondHand = document.getElementById('secondHand');
      const monoWrap = document.getElementById('monoWrap');
      const switchRow = document.getElementById('switchRow');
      const switchTrack = document.getElementById('switchTrack');
      const switchKnob = document.getElementById('switchKnob');
      const chargePanel = document.getElementById('chargePanel');
      const chargeDigital = document.getElementById('chargeDigital');
      const chargeFill = document.getElementById('chargeFill');
    
      const HOUR_LAP      = 11.5*3600;  // 時針が一周するのに要する「動作時間」(秒)=23時間で2周
      const FORCED_PERIOD = 23*3600;    // 強制停止が起きる周期(秒)=動作フェーズの長さ
      const MAX_CHARGE    = 1*3600;     // Charged Timeの上限(秒)= 停止フェーズの長さ(実1時間)
      const CHARGE_RATE   = MAX_CHARGE/FORCED_PERIOD; // 動作中、実1秒あたりに増えるCharged Time(秒)
      const RELEASE_RATE  = 1;          // 停止中、実1秒あたりに減るCharged Time(秒)
    
      // 0時から動いていたと仮定して、現在の本来の状態(R・Charged Time・動作/停止)を逆算する
      const loadDate = new Date();
      const sinceMidnight = loadDate.getHours()*3600 + loadDate.getMinutes()*60 + loadDate.getSeconds() + loadDate.getMilliseconds()/1000;
    
      let R, charge, isPaused, isForced;
      if(sinceMidnight < FORCED_PERIOD){
        R = sinceMidnight;
        charge = Math.min(MAX_CHARGE, R*CHARGE_RATE);
        isPaused = false;
        isForced = false;
      } else {
        R = FORCED_PERIOD; // 23時の到達点で凍結
        const pauseElapsed = sinceMidnight - FORCED_PERIOD;
        charge = Math.max(0, MAX_CHARGE - pauseElapsed*RELEASE_RATE);
        isPaused = charge > 0;
        isForced = isPaused;
      }
      let nextLap = FORCED_PERIOD * (Math.floor(R/FORCED_PERIOD) + 1); // 次に強制停止が起きるRの値
      let lastT = performance.now();
    
      function updateSwitchUI(){
        switchTrack.disabled = isForced;
        switchTrack.setAttribute('aria-checked', String(isPaused));
        const onState = isPaused ? 'state-release' : 'state-charge';
        const offState = isPaused ? 'state-charge' : 'state-release';
        [switchRow, switchTrack, chargePanel].forEach(el => { el.classList.remove(offState); el.classList.add(onState); });
        switchKnob.classList.remove(isPaused ? 'pos-charge' : 'pos-release');
        switchKnob.classList.add(isPaused ? 'pos-release' : 'pos-charge');
      }
    
      switchTrack.addEventListener('click', () => {
        if(isForced) return;
        isPaused = !isPaused;
        updateSwitchUI();
      });
    
      function frame(now){
        const dt = (now-lastT)/1000;
        lastT = now;
    
        if(!isPaused){
          R += dt;
          charge = Math.min(MAX_CHARGE, charge + dt*CHARGE_RATE);
          if(R >= nextLap){
            nextLap += FORCED_PERIOD;
            isPaused = true;
            isForced = true;
            updateSwitchUI();
          }
        } else {
          charge = Math.max(0, charge - dt*RELEASE_RATE);
          if(charge === 0){
            isPaused = false;
            isForced = false;
            updateSwitchUI();
          }
        }
    
        // すべての針はRから決まる(止まっている間はRが増えないので、針も一緒に止まる)
        // 時針1周 = HOUR_LAP、分針1周 = HOUR_LAP/12、秒針1周 = HOUR_LAP/720
        // これにより時・分・秒が一貫した速さで連動する
        const CLOCK_MINUTE = HOUR_LAP / 12;    // 1周 = 3450秒(実時間)
        const CLOCK_SECOND = CLOCK_MINUTE / 60; // 1周 = 57.5秒(実時間)
    
        const hourFrac   = (R % HOUR_LAP)    / HOUR_LAP;
        const minuteFrac = (R % CLOCK_MINUTE) / CLOCK_MINUTE;
        const secondFrac = (R % CLOCK_SECOND) / CLOCK_SECOND;
    
        hourHand.style.transform = `translateX(-50%) rotate(${hourFrac*360}deg)`;
        minuteHand.style.transform = `translateX(-50%) rotate(${minuteFrac*360}deg)`;
        secondHand.style.transform = `translateX(-50%) rotate(${secondFrac*360}deg)`;
    
        chargeFill.style.width = (charge/MAX_CHARGE*100) + '%';
        const mm = Math.floor(charge/60);
        const ss = Math.floor(charge%60);
        chargeDigital.textContent = String(mm).padStart(2,'0') + ':' + String(ss).padStart(2,'0');
    
        monoWrap.classList.toggle('paused', isPaused);
    
        requestAnimationFrame(frame);
      }
    
      updateSwitchUI();
      requestAnimationFrame(frame);
    </script>
    </body>
    </html>
     
     
    rasen_si loves ethics and logics.

    あなたへのおすすめ