<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
  xmlns:atom="http://www.w3.org/2005/Atom"
  xmlns:content="http://purl.org/rss/1.0/modules/content/"
  xmlns:dc="http://purl.org/dc/elements/1.1/"
>
  <channel>
    <title>zera</title>
    <link>https://dunkirk.sh</link>
    <description>The home site of kieran klukas</description>
    <language>en</language>
    <generator>Zola</generator>
    <atom:link href="https://dunkirk.sh/rss.xml" rel="self" type="application/rss+xml"/>
    <lastBuildDate>Sat, 25 Apr 2026 00:00:00 +0000</lastBuildDate>
    
    <item>
      <title>Reverse engineering the FRC SystemCore image</title>
      <link>https://dunkirk.sh/blog/frc-systemcore-image/</link>
      <guid isPermaLink="true">https://dunkirk.sh/blog/frc-systemcore-image/</guid>
      <pubDate>Sat, 25 Apr 2026 00:00:00 +0000</pubDate>
      
      
      <dc:creator>Kieran Klukas</dc:creator>
      
      
        
      <category>frc</category>
        
      <category>reverse engineering</category>
        
      
      <description>
        
          &amp;lt;p&amp;gt;FIRST announced the SystemCore several months ago now and the beta software started rolling out recently and becoming way more polished so with the release of the &amp;lt;a rel=&amp;quot;noopener external&amp;quot; target=&amp;quot;_blank&amp;quot; href=&amp;quot;https:&#x2F;&#x2F;github.com&#x2F;wpilibsuite&#x2F;FirstDriverStation-Public&amp;quot;&amp;gt;new driver station&amp;lt;&#x2F;a&amp;gt; I decided it was time to dig into the firmware image and see what I could find!&amp;lt;&#x2F;p&amp;gt;
        
      </description>
      <content:encoded><![CDATA[<p>FIRST announced the SystemCore several months ago now and the beta software started rolling out recently and becoming way more polished so with the release of the <a rel="noopener external" target="_blank" href="https://github.com/wpilibsuite/FirstDriverStation-Public">new driver station</a> I decided it was time to dig into the firmware image and see what I could find!</p>
<span id="continue-reading"></span><figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;mGIuJH5L1I-o.webp" alt="the SystemCore in all its glory"  />
    </div>
    
    <figcaption><p>they really did a great job of making it look nice and polished</p>
</figcaption>
    
</figure>
<p>The SystemCore is based on a Raspberry Pi Compute Module 5 with a 16 GB eMMC (which formats to about 14.5 GB). The firmware image extracts to 10 GB but this only covers the partition layout, not the full eMMC – the remaining space is unpartitioned. Considering that the original zip file is only 1.8 GB there is likely quite a bit of empty / wasted space within the partitions. It is interesting that they didn’t choose to go the auto expanding route but I’m guessing they did this for ease of use.</p>
<table><thead><tr><th>Partition</th><th>Type</th><th>Start LBA</th><th>Sectors</th><th>Size</th><th>Description</th></tr></thead><tbody>
<tr><td>P0</td><td>FAT32 (0x0C)</td><td>1</td><td>131,072</td><td>64MB</td><td>Boot partition</td></tr>
<tr><td>P1</td><td>Linux (0x83)</td><td>131,073</td><td>10,485,760</td><td>5GB</td><td>Root filesystem A</td></tr>
<tr><td>P2</td><td>Linux (0x83)</td><td>10,616,833</td><td>10,485,760</td><td>5GB</td><td>Root filesystem B (fallback)</td></tr>
<tr><td>P3</td><td>Linux (0x83)</td><td>21,102,593</td><td>524,288</td><td>256MB</td><td>Data partition (empty/sparse)</td></tr>
</tbody></table>
<p>There is an A and B partition which is used by the <code>limelight_updatemanager</code> to atomically update the system preventing broken updates from bricking the system which is quite nice. This is done at the kernel level with the <code>tryboot_a_b</code> flag.</p>
<p>The system is based on the 6.6.64-rt47-v8-16k (PREEMPT_RT real-time) kernel and appears to be entirely custom built using <a rel="noopener external" target="_blank" href="https://buildroot.org/">buildroot</a> and does not contain any standard package managers aside from the custom ui one that is supposed to allow for install of Elastic and other tools in the future.</p>
<p>The CM5 carrier board has 5 can FD buses running on MCP2518FD controllers all running on 40Mhz oscillators via SPI. There was a comment in the device tree config <code># BETA SYSTEMCORE: CAN2 on SPI3 (MOSI=6, MISO=5, CLK=7, CS=27, INT=9)</code> so I’m not sure if this is the final layout of the offical release this fall. All of the controllers are configured to run at 1Mbps CAN FD with txqueuelen=1000.</p>
<table><thead><tr><th>CAN Bus</th><th>SPI Port</th><th>CS Pin</th><th>INT Pin</th><th>Overlay</th></tr></thead><tbody>
<tr><td><code>can_s0</code></td><td>SPI2</td><td>CS0=GPIO0</td><td>GPIO22</td><td><code>sc-mcp2518-can0-spi2</code></td></tr>
<tr><td><code>can_s1</code></td><td>SPI2</td><td>CS1=GPIO24</td><td>GPIO26</td><td><code>sc-mcp2518-can1-spi2</code></td></tr>
<tr><td><code>can_s2</code></td><td>SPI3</td><td>CS0=GPIO27</td><td>GPIO9</td><td><code>sc-mcp2518-can2-spi3</code> (BETA)</td></tr>
<tr><td><code>can_s3</code></td><td>SPI1</td><td>CS0=GPIO25</td><td>GPIO17</td><td><code>sc-mcp2518-can3-spi1-beta</code></td></tr>
<tr><td><code>can_s4</code></td><td>SPI1</td><td>CS1=GPIO18</td><td>GPIO8</td><td><code>sc-mcp2518-can4-spi1-beta</code></td></tr>
</tbody></table>
<p>There are two different usb network interfaces with the ECM one for linux/macOS and the RNDIS for windows both exposed via usb gadget mode. Interestingly the can buses also show up here.</p>
<table><thead><tr><th>Interface</th><th>Purpose</th><th>IP Range</th></tr></thead><tbody>
<tr><td><code>usb0</code></td><td>ECM USB network to DS</td><td>172.27.0-15.x (DHCP)</td></tr>
<tr><td><code>usb1</code></td><td>RNDIS USB network to DS</td><td>172.26.0-15.x (DHCP)</td></tr>
<tr><td><code>wlan0</code></td><td>WiFi access point</td><td>172.30.0.1/24 (hostapd)</td></tr>
<tr><td><code>eth0</code></td><td>Ethernet</td><td>DHCP client</td></tr>
<tr><td><code>can_s0</code>-<code>can_s4</code></td><td>CAN FD buses</td><td>N/A</td></tr>
</tbody></table>
<h3 id="services">Services</h3>
<p>Now for the interesting part. There are 18 systemd services on the image and I tried to rougly split them up by type.</p>
<h4 id="mrccommdaemon">MrcCommDaemon</h4>
<p>The <code>MrcCommDaemon</code> (4.5MB, stripped aarch64) is kind of the main program. This is the DS communication daemon - it runs the NT4 server on port 5812, handles the UDP control packet protocol, publishes robot state, and subscribes to DS commands. It’s the equivalent of the NI FRC NetworkTables server on a roboRIO, except it also speaks the DS control protocol directly.</p>
<p>The most interesting discovery here is the <strong>topic namespace split</strong>. On the DS side, everything is published under <code>/Dscomm/</code> (e.g. <code>/Dscomm/Control/Enabled</code>, <code>/Dscomm/Status/BatteryVoltage</code>). But on the robot side, MrcCommDaemon publishes the same logical data under <code>/Netcomm/</code> (e.g. <code>/Netcomm/Control/Enabled</code>, <code>/Netcomm/Status/BatteryVoltage</code>). The daemon acts as a bridge between the two - translating between DS UDP packets and NT4 topics with the appropriate prefix. This was discovered due to some rev work that will :tw_hand_with_index_and_middle_fingers_crossed: hopefully get published soon.</p>
<p>It also reads CTRE device status from a custom kernel module at <code>/sys/kernel/can_heartbeat/controldata</code> and system telemetry from <code>/sys/cpu</code>, <code>/sys/battery</code>, <code>/sys/ram</code>, and <code>/sys/storage</code>.</p>
<h4 id="can-bus-stack">CAN Bus Stack</h4>
<p>Five services manage the CAN FD buses:</p>
<table><thead><tr><th>Service</th><th>Binary</th><th>Description</th></tr></thead><tbody>
<tr><td><code>limelight_canbusprocess</code></td><td>shell script</td><td>Configures can_s0–s4 at 1Mbps, loads <code>can_heartbeat</code> and <code>i2c-dev</code> modules</td></tr>
<tr><td><code>limelight_canbusloadmon</code></td><td><code>canbusloadmon</code> (22KB)</td><td>Monitors per-bus load and publishes to NT4</td></tr>
<tr><td><code>limelight_canbussniffer</code></td><td><code>canbussniffer</code> (142KB)</td><td>WebSocket server on port 5800 streaming live CAN frames</td></tr>
<tr><td><code>limelight_canbuswatchdog</code></td><td><code>canbuswatchdog</code> (26KB)</td><td>Monitors all 5 interfaces, writes faults to <code>/sys/faults/</code></td></tr>
<tr><td><code>can_heartbeat</code></td><td>kernel module</td><td>Bridges CAN heartbeat data to sysfs for MrcCommDaemon</td></tr>
</tbody></table>
<p>The CAN bus setup script is also interesting it sleeps 4 seconds for some reason, then brings up all five interfaces:</p>
<pre class="giallo z-code"><code data-lang="shellscript"><span class="giallo-l"><span class="z-entity z-name z-function">sleep</span><span class="z-constant z-numeric"> 4</span><span class="z-punctuation"> &amp;&amp;</span><span class="z-constant z-character z-escape"> \</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">  ip</span><span class="z-string"> link set can_s0 type can bitrate</span><span class="z-constant z-numeric"> 1000000</span><span class="z-punctuation"> &amp;&amp;</span><span class="z-constant z-character z-escape"> \</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">  ip</span><span class="z-string"> link set can_s0 txqueuelen</span><span class="z-constant z-numeric"> 1000</span><span class="z-punctuation"> &amp;&amp;</span><span class="z-entity z-name z-function"> ip</span><span class="z-string"> link set can_s0 up</span><span class="z-punctuation"> &amp;&amp;</span><span class="z-constant z-character z-escape"> \</span></span>
<span class="giallo-l"><span class="z-punctuation z-definition z-comment z-comment">  # ... repeated for can_s1 through can_s4 ...</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">  modprobe</span><span class="z-string"> can_heartbeat</span><span class="z-punctuation"> &amp;&amp;</span><span class="z-entity z-name z-function"> modprobe</span><span class="z-string"> i2c-dev</span></span></code></pre>
<p>The sniffer on port 5800 is going to be really fun to play with you can connect from a browser and watch CAN traffic in real time. The <code>can_heartbeat</code> kernel module is custom out-of-tree and bridges CAN data to sysfs for MrcCommDaemon to read.</p>
<h4 id="networking">Networking</h4>
<p>The networking is surprisingly complex but does make sense. USB gadget mode provides two virtual network interfaces to the DS simultaneously - ECM for macOS/Linux and RNDIS for Windows - over the same USB connection. Each has its own DHCP range subnetted by team number (16 subnets of 11 addresses each). The WiFi AP runs a separate <code>dnsmasq</code> instance with DNS completely disabled (<code>port=0</code>).</p>
<table><thead><tr><th>Service</th><th>Description</th></tr></thead><tbody>
<tr><td><code>limelight_gadget</code></td><td>Configures USB composite device (ECM + RNDIS) via configfs</td></tr>
<tr><td><code>limelight_dnsmasq</code></td><td>DHCP server on usb0 + usb1 (172.27.x / 172.26.x)</td></tr>
<tr><td><code>limelight_dnsmasqwifi</code></td><td>DHCP server on wlan0 (172.30.0.x, no DNS)</td></tr>
<tr><td><code>limelight_accesspoint</code></td><td><code>hostapd</code> - 5GHz 802.11ac AP, SSID <code>SYSTEMCORE</code>, WPA2-PSK</td></tr>
<tr><td><code>limelight_irqconf</code></td><td>Pins eth0 IRQ to CPU core 2 for deterministic latency</td></tr>
<tr><td><code>limelight_networkresponder</code></td><td>Network discovery responder (V1 single-IP, V2 all-interfaces)</td></tr>
</tbody></table>
<p>The <code>irqconf.sh</code> script is interesting as it finds eth0’s interrupt and sets <code>smp_affinity</code> to core 2 which should help with deterministic network processing.</p>
<p>The WiFi AP config is also interesting. It uses 5GHz 802.11ac with automatic channel selection, but only on non-DFS channels (36, 40, 44, 48, 149, 153, 157, 161, 165) - skipping the UNII-2 bands that require radar detection. Max 10 clients. This looks like its probably meant for pit use?</p>
<h4 id="hardware-daemons">Hardware Daemons</h4>
<p>These four services bridge physical hardware to NT4:</p>
<table><thead><tr><th>Service</th><th>Binary</th><th>Description</th></tr></thead><tbody>
<tr><td><code>limelight_iodaemon</code></td><td><code>iodaemon</code> (62KB)</td><td>USB bridge to LED/status board via libusb, runs at RT FIFO priority 39</td></tr>
<tr><td><code>expansionhubdaemon.service</code></td><td><code>ExpansionHubDaemon</code> (4.1MB)</td><td>REV Expansion Hub driver over USB-serial (RHSP protocol)</td></tr>
<tr><td><code>powerdistribution.service</code></td><td><code>PowerDistributionDaemon</code> (3.9MB)</td><td>PDH/PDP monitoring, publishes <code>/pd/</code> NT4 topics</td></tr>
<tr><td><code>limelight_picoflasherprocess</code></td><td><code>picoflasherprocess</code></td><td>Auto-flashes any Pico that enters bootloader mode via USB mass storage</td></tr>
</tbody></table>
<p>The IODaemon is fairly interesting - it’s a small libusb-based binary that sends LED updates, CAN status, GPIO state, IP address, and team number to the external pico for the display. It reads the robot enabled state from <code>/sys/kernel/can_heartbeat/enabledro</code> and runs at the highest real-time priority of any service. This is what drives the physical status LEDs on the SystemCore case.</p>
<p>The ExpansionHubDaemon speaks the RHSP (REV Hardware Server Protocol) over USB-serial and publishes NT4 topics under <code>/rhsp/</code> for motor control, encoder reading, servo control, and PID. It’s the built-in REV Expansion Hub support.</p>
<h4 id="system-management">System Management</h4>
<table><thead><tr><th>Service</th><th>Binary</th><th>Port</th><th>Description</th></tr></thead><tbody>
<tr><td><code>limelight_hwmon</code></td><td><code>hwmon</code> (535KB)</td><td>-</td><td>REST API for CPU, RAM, disk, kernel, sensors</td></tr>
<tr><td><code>limelight_diagnosticsprocess</code></td><td><code>diagnosticsprocess</code> (411KB)</td><td>4800</td><td>Web config panel (team number, WiFi, networking)</td></tr>
<tr><td><code>limelight_packagemanager</code></td><td><code>packagemanager</code> (198KB)</td><td>4803</td><td>IPK package manager for deploying user code</td></tr>
<tr><td><code>limelight_updatemanager</code></td><td><code>updatemanager</code> (158KB)</td><td>4804</td><td>A/B partition OTA updates</td></tr>
</tbody></table>
<p>The diagnostics process is the web configuration API. It handles:</p>
<ul>
<li><code>GET/POST /api/team</code> - set team number (written to <code>/sys/team</code>)</li>
<li><code>GET/POST /api/wifi</code> - configure SSID, password, WPA (rewrites <code>hostapd.conf</code>, restarts the AP)</li>
<li><code>GET/POST /api/network</code> - configure eth0 networking (rewrites <code>dhcpcd.conf</code>)</li>
<li><code>GET /api/health</code> - health check</li>
<li><code>GET /api/oshash</code> - OS version hash (curious whether this will get used to validate the SystemCore image on the FMS?)</li>
</ul>
<p>The update manager handles the A/B partition scheme. It receives a partition image, DD-extracts it to the inactive rootfs partition, then mounts the boot partition read-write and updates <code>autoboot.txt</code>. The reboot uses the Pi’s <code>tryboot</code> mechanism - <code>sudo reboot "0 tryboot"</code> - which boots into the new partition once, and only commits it as the default after validation. If it fails, you just power cycle and it falls back. Classic A/B update pattern, well executed.</p>
<p>The package manager uses <code>opkg</code> under the hood and reads <code>X-Port</code>, <code>X-Launch-Command</code>, and <code>X-Auto-Start</code> from IPK control files. This is what creates the <code>/home/systemcore/robotCommand</code> script that <code>robot.service</code> executes - it’s generated from the installed package metadata, not baked into the image.</p>
<h4 id="radiodaemon">RadioDaemon</h4>
<p>Thad House (WPILib dev) confirmed that <code>RadioDaemon</code>, <code>ExpansionHubDaemon</code>, and <code>PowerDistributionDaemon</code> are all <a rel="noopener external" target="_blank" href="https://github.com/wpilibsuite/scservices">open source</a>. The RadioDaemon is meant to talk to the HTTP server on the FRC field radio and relay link quality info back to the DS over NT4 - but it hasn’t actually been written yet, which explains why the binary has NT4 plumbing but zero radio-specific strings. It’s a stub. The actual WiFi AP is handled entirely by <code>hostapd</code> and <code>dnsmasq</code> directly.</p>
<h4 id="robot-code-runner">Robot Code Runner</h4>
<pre class="giallo z-code"><code data-lang="ini"><span class="giallo-l"><span class="z-punctuation">[</span><span class="z-entity z-name z-section z-group-title z-ini">Service</span><span class="z-punctuation">]</span></span>
<span class="giallo-l"><span class="z-keyword z-other z-definition z-ini">Type</span><span class="z-punctuation z-separator z-key-value">=</span><span class="z-source">simple</span></span>
<span class="giallo-l"><span class="z-keyword z-other z-definition z-ini">User</span><span class="z-punctuation z-separator z-key-value">=</span><span class="z-source">systemcore</span></span>
<span class="giallo-l"><span class="z-keyword z-other z-definition z-ini">LimitRTPRIO</span><span class="z-punctuation z-separator z-key-value">=</span><span class="z-source">50</span></span>
<span class="giallo-l"><span class="z-keyword z-other z-definition z-ini">ExecStartPre</span><span class="z-punctuation z-separator z-key-value">=</span><span class="z-source">/bin/bash -c </span><span class="z-punctuation z-definition z-string z-string">&#39;\</span></span>
<span class="giallo-l"><span class="z-string">  timeout=15; echo &quot;waiting for can interfaces&quot;; \</span></span>
<span class="giallo-l"><span class="z-string">  while [[ $timeout &gt; 0 ]]; do \</span></span>
<span class="giallo-l"><span class="z-string">    good=true; \</span></span>
<span class="giallo-l"><span class="z-string">    for can in can_s0 can_s1 can_s2 can_s3 can_s4; do \</span></span>
<span class="giallo-l"><span class="z-string">      if ! ip link show $can up 2&gt;/dev/null | grep -q &quot;state UP&quot;; then \</span></span>
<span class="giallo-l"><span class="z-string">        good=false; fi; done; \</span></span>
<span class="giallo-l"><span class="z-string">    if [[ $good = true ]]; then exit 0; fi; \</span></span>
<span class="giallo-l"><span class="z-string z-punctuation z-definition z-string">    sleep 1; timeout=$((timeout-1)); done&#39;</span></span>
<span class="giallo-l"><span class="z-keyword z-other z-definition z-ini">ExecStart</span><span class="z-punctuation z-separator z-key-value">=</span><span class="z-source">/bin/bash /home/systemcore/robotCommand</span></span>
<span class="giallo-l"><span class="z-keyword z-other z-definition z-ini">Restart</span><span class="z-punctuation z-separator z-key-value">=</span><span class="z-source">always</span></span>
<span class="giallo-l"><span class="z-keyword z-other z-definition z-ini">RestartSec</span><span class="z-punctuation z-separator z-key-value">=</span><span class="z-source">3</span></span></code></pre>
<p>The <code>robot.service</code> runs as the <code>systemcore</code> user (UID 105) with real-time priority 50. It waits up to 15 seconds for all five CAN interfaces to come up before starting user code. The <code>robotCommand</code> script itself isn’t in the image - it’s created at runtime by the package manager when user code is deployed. The <code>ConditionPathExists=/home/systemcore/robotCommand</code> directive means the service won’t even attempt to start until code has been deployed, which is a nice touch.</p>
<h3 id="smart-io">Smart IO</h3>
<p>The SystemCore integrates an RP2350 microcontroller for Smart IO - the successor to the Raspberry Pi Pico’s PIO (Programmable IO). Unlike a standalone Pico, this RP2350 has no flash attached; instead, the CM5 loads its firmware into RAM over a serial interface at boot time. The corresponding Linux kernel modules handle this loading sequence, pushing the UF2-style firmware blob from the <code>picoflasherprocess</code> (which carries an embedded <code>fw.uf2</code> at 127 KB) into the RP2350’s RAM before the IO daemon starts. The <code>iodaemon</code> then communicates with the RP2350 over USB (via libusb) to drive the status LEDs, relay CAN bus info, and handle GPIO.</p>
<h3 id="odds-and-ends">Odds and Ends</h3>
<p>The image ships with a VS Code server on port 4900 and a <code>ttyd</code> web terminal on port 4901. On the discovery side, the SystemCore advertises itself via mDNS as both <code>_SystemCore._tcp</code> and the legacy NI services (<code>_ni._tcp</code>, <code>_ni-rt._tcp</code>). It currently uses the roboRIO hostname pattern <code>roboRIO-{team}-FRC</code> alongside <code>SystemCore-{team}-FIRST</code>. This was done as a backwards compatibilty move during the beta period but will be removed in the full release in 2027.</p>
<p>One other oddity is the <code>xone-*</code> kernel modules - Xbox controller drivers for the wireless dongle, gamepad, chatpad, and headset. In normal FRC the flow is controllers -&gt; DS -&gt; robot, so having Xbox drivers on the robot side seems pointless. These are out-of-tree modules that someone deliberately added to the Buildroot config, but my guess is they’re leftover from Limelight’s general-purpose image config rather than something the SystemCore actually uses. Still, it raises the question of whether they’re planning some kind of direct-attach mode for FTC maybe?.</p>
<p>Finally, the hostname is just <code>robot</code> and <code>/etc/os-release</code> identifies as <code>limelightos_systemcore_beta</code> with a git-hash version string. The whole userspace is owned by UID 105 / GID 113 (<code>systemcore</code>).</p>
<p>I will update this post if I find more interesting things over the next few months and I have some interesting ideas in the pipeline for testing with this 👀</p>
<h4 id="errata">Errata</h4>
<ul>
<li>2026-04-25 16:50 - Added clarification from Thad House on radio daemon not being written yet</li>
<li>2026-04-25 16:55 - Added clarification that dual hostnames will be removed for production release</li>
<li>2026-04-25 21:25 - Fixed eMMC size (16 GB physical, ~14.5 GB formatted; 10 GB is image partitions only), added RP2350 Smart IO section, corrected SystemCore capitalization</li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>FRC REBUILT Points Calculator</title>
      <link>https://dunkirk.sh/blog/frc-rebuilt-calculator/</link>
      <guid isPermaLink="true">https://dunkirk.sh/blog/frc-rebuilt-calculator/</guid>
      <pubDate>Sun, 11 Jan 2026 00:00:00 +0000</pubDate>
      
      
      <dc:creator>Kieran Klukas</dc:creator>
      
      
        
      <category>frc</category>
        
      <category>robotics</category>
        
      <category>calculator</category>
        
      
      <description>
        
          &amp;lt;p&amp;gt;I was manually doing bps calculations yesterday at kickoff and figured there must be a better way so here you go :)&amp;lt;&#x2F;p&amp;gt;
        
      </description>
      <content:encoded><![CDATA[<p>I was manually doing bps calculations yesterday at kickoff and figured there must be a better way so here you go :)</p>
<span id="continue-reading"></span><h3 id="match-timeline">Match Timeline</h3>
<p>A match lasts <strong>2 minutes and 40 seconds</strong> (160 seconds total):</p>
<table><thead><tr><th>Period</th><th>Duration</th><th>Hub Status</th></tr></thead><tbody>
<tr><td><strong>Autonomous</strong></td><td>20s</td><td>Both Hubs Active</td></tr>
<tr><td><strong>Transition Shift</strong></td><td>10s</td><td>Both Hubs Active</td></tr>
<tr><td><strong>Shift 1</strong></td><td>25s</td><td>One Active / One Inactive</td></tr>
<tr><td><strong>Shift 2</strong></td><td>25s</td><td>One Active / One Inactive</td></tr>
<tr><td><strong>Shift 3</strong></td><td>25s</td><td>One Active / One Inactive</td></tr>
<tr><td><strong>Shift 4</strong></td><td>25s</td><td>One Active / One Inactive</td></tr>
<tr><td><strong>End Game</strong></td><td>30s</td><td>Both Hubs Active</td></tr>
</tbody></table>
<p>Winning autonomous affects your Hub status so if your alliance scores the most Fuel in AUTO, your Hub is <strong>Inactive</strong> for Shifts 1 &amp; 3, and <strong>Active</strong> for Shifts 2 &amp; 4.</p>
<h3 id="ranking-points">Ranking points</h3>
<p>For Regional/District events:</p>
<ul>
<li><strong>Energized RP:</strong> Score <strong>100 Fuel</strong> (1 RP)</li>
<li><strong>Supercharged RP:</strong> Score <strong>360 Fuel</strong> (1 RP)</li>
<li><strong>Traversal RP:</strong> Earn <strong>50 Tower points</strong> (1 RP)</li>
</ul>
<h3 id="bps-calculator">BPS calculator</h3>
<p>Use this calculator to determine the balls per second (BPS) your robot needs to achieve ranking point thresholds. Adjust parameters based on your robot’s capabilities and strategy.</p>
<p>The max bps is only used in the simulation of the match while the results section is doing back propagation to figure out the necessary BPS needed to hit that ranking point no matter how high that is. If one of the results says “N/A” that means that your reload time is too high and eats up enough shooting time its no longer possible to hit that ranking point threshold.</p>
<div id="frc-calculator" class="frc-calculator">
  <div class="frc-calculator-inner">
    <div class="calc-section">
      <h3>Robot Parameters</h3>

    <div class="input-group">
      <label for="ballCapacity">Ball Capacity:</label>
      <input type="number" id="ballCapacity" value="8" min="1" max="10">
      <span class="unit">balls</span>
    </div>

    <div class="input-group">
      <label for="reloadTime">Reload Time:</label>
      <input type="number" id="reloadTime" value="3" min="0" step="0.5">
      <span class="unit">seconds</span>
    </div>

    <div class="input-group">
      <label for="shooterBPS">Shooter BPS (max):</label>
      <input type="number" id="shooterBPS" value="4" min="0.1" step="0.1">
      <span class="unit">balls/s</span>
    </div>

    <div class="input-group">
      <label for="numRobots">Number of Robots:</label>
      <input type="number" id="numRobots" value="3" min="1" max="3">
      <span class="unit">robots</span>
    </div>

    <div class="input-group">
      <label for="graceTime">Hub Grace Period:</label>
      <input type="number" id="graceTime" value="3" min="0" max="10" step="0.5">
      <span class="unit">seconds</span>
    </div>
  </div>

  <div class="calc-section">
    <h3>Match Strategy</h3>

    <div class="input-group checkbox-group">
      <input type="checkbox" id="includeAuto" checked>
      <label for="includeAuto">Include Autonomous Period</label>
    </div>

    <div class="input-group indent-group" id="autoTimeGroup">
      <label for="autoShootTime">Auto Shooting Time:</label>
      <input type="number" id="autoShootTime" value="20" min="0" max="20" step="0.5">
      <span class="unit">seconds</span>
    </div>

    <div class="input-group checkbox-group">
      <input type="checkbox" id="includeEndgame" checked>
      <label for="includeEndgame">Include Endgame Period</label>
    </div>

    <div class="input-group indent-group" id="endgameTimeGroup">
      <label for="endgameShootTime">Endgame Shooting Time:</label>
      <input type="number" id="endgameShootTime" value="30" min="0" max="30" step="0.5">
      <span class="unit">seconds</span>
    </div>

    <div class="input-group checkbox-group">
      <input type="checkbox" id="wonAuto">
      <label for="wonAuto">Won Autonomous Period</label>
    </div>
  </div>

  <div class="calc-section">
    <h3>Ranking Point Thresholds</h3>

    <div class="input-group">
      <label for="energizedThreshold">Energized Threshold:</label>
      <input type="number" id="energizedThreshold" value="100" min="1" max="500">
      <span class="unit">fuel</span>
    </div>

    <div class="input-group">
      <label for="superchargedThreshold">Supercharged Threshold:</label>
      <input type="number" id="superchargedThreshold" value="360" min="1" max="1000">
      <span class="unit">fuel</span>
    </div>
  </div>

  <div class="calc-section animation">
    <h3>Match Simulation</h3>

    <div class="simulation-container">
      <div class="simulation-header">
        <div class="sim-stat">
          <span class="sim-label">Time:</span>
          <span class="sim-value" id="simTime">0.0s</span>
        </div>
        <div class="sim-stat">
          <span class="sim-label">Balls in Hopper:</span>
          <span class="sim-value" id="simBalls">0</span>
        </div>
        <div class="sim-stat">
          <span class="sim-label">Total Scored:</span>
          <span class="sim-value" id="simScored">0</span>
        </div>
        <div class="sim-stat">
          <span class="sim-label">Status:</span>
          <span class="sim-value" id="simStatus">Ready</span>
        </div>
      </div>

      <canvas id="matchCanvas" width="800" height="200"></canvas>

      <div class="timeline-container">
        <div class="timeline-labels" id="timelineLabels"></div>
        <input type="range" id="timelineScrubber" min="0" max="168" value="0" step="0.1" class="timeline-scrubber">
      </div>

      <div class="simulation-controls">
        <button id="playPauseBtn" class="sim-button">Play</button>
        <button id="resetBtn" class="sim-button">Reset</button>
        <div class="speed-controls">
          <label for="speedSelect">Speed:</label>
          <select id="speedSelect">
            <option value="0.5">0.5x</option>
            <option value="1" selected>1x</option>
            <option value="2">2x</option>
            <option value="4">4x</option>
            <option value="8">8x</option>
          </select>
        </div>
      </div>
    </div>
  </div>

  <div class="calc-section results">
    <h3>Results</h3>

    <div class="result-box">
      <div class="result-label">Total Active Scoring Time:</div>
      <div class="result-value" id="totalTime">-</div>
    </div>

    <div class="result-box">
      <div class="result-label">Effective Cycle Time:</div>
      <div class="result-value" id="cycleTime">-</div>
    </div>

    <div class="result-box highlight">
      <div class="result-label">Alliance BPS for Energized RP:</div>
      <div class="result-value" id="energizedAllianceBPS">-</div>
    </div>

    <div class="result-box">
      <div class="result-label">Per Robot BPS for Energized RP:</div>
      <div class="result-value" id="energizedRobotBPS">-</div>
    </div>

    <div class="result-box highlight">
      <div class="result-label">Alliance BPS for Supercharged RP:</div>
      <div class="result-value" id="superchargedAllianceBPS">-</div>
    </div>

    <div class="result-box">
      <div class="result-label">Per Robot BPS for Supercharged RP:</div>
      <div class="result-value" id="superchargedRobotBPS">-</div>
    </div>
  </div>
  </div>
</div>

<style>
.frc-calculator {
  background-color: var(--accent);
  border-bottom: 5px solid var(--bg-light);
  border-radius: 7px 7px 10px 10px;
  padding: 0.75rem;
  margin: 2rem 0;
}

.frc-calculator-inner {
  background-color: var(--nightshade-violet);
  border-radius: 0.3rem;
  padding: 1rem;
}

.calc-section {
  margin-bottom: 1.5rem;
}

.calc-section:last-child {
  margin-bottom: 0;
}

.calc-section h3 {
  margin: 0 0 1rem 0;
  padding: 0.22em 0.4em 0.22em 0.4em;
  font-size: 1.25rem;
  background-color: var(--accent);
  border-bottom: 5px solid var(--bg-light);
  border-radius: 0.2em 0.2em 0.27em 0.27em;
  color: var(--accent-text);
  width: fit-content;
}

.input-group {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-bottom: 0.75rem;
}

.input-group label {
  flex: 1;
  font-weight: 500;
  color: var(--text);
}

.input-group input[type="number"] {
  width: 100px;
  padding: 0.5rem;
  border: 2px solid var(--ultra-violet);
  border-radius: var(--standard-border-radius);
  background-color: var(--bg);
  color: var(--text);
  font-size: 1rem;
  font-family: inherit;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
  transition: border-color 120ms ease, box-shadow 120ms ease;
}

.input-group input[type="number"]:focus {
  outline: none;
  border-color: var(--rose-quartz);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
}

.input-group .unit {
  color: var(--text-light);
  font-size: 0.875rem;
  min-width: 60px;
}

.checkbox-group {
  margin-bottom: 0.5rem;
}

.checkbox-group input[type="checkbox"] {
  vertical-align: middle;
  position: relative;
  width: 16px;
  height: 16px;
  cursor: pointer;
  margin: 0;
  margin-right: 0.5rem;
  border: 2px solid var(--ultra-violet);
  border-radius: var(--standard-border-radius);
  background-color: var(--bg);
  transition: all 120ms ease;
}

.checkbox-group input[type="checkbox"]:checked {
  background-color: var(--rose-quartz);
  border-color: var(--rose-quartz);
}

.checkbox-group input[type="checkbox"]:hover {
  border-color: var(--pink-puree);
}

.checkbox-group label {
  cursor: pointer;
  user-select: none;
  display: inline-block;
}

.indent-group {
  margin-left: 2rem;
  margin-bottom: 1rem;
}

.results {
  background-color: var(--purple-night);
  padding: 1rem;
  border-radius: 0.3rem;
  border: 2px solid var(--ultra-violet);
}

.result-box {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0.75rem;
  margin-bottom: 0.5rem;
  background-color: var(--bg);
  border-radius: var(--standard-border-radius);
}

.result-box:last-child {
  margin-bottom: 0;
}

.result-box.highlight {
  background-color: var(--ultra-violet);
  border: 1px solid var(--rose-quartz);
}

.result-label {
  font-weight: 500;
  color: var(--text);
}

.result-value {
  font-weight: 700;
  font-size: 1.125rem;
  color: var(--pink-puree);
  font-family: var(--mono-font);
}

@media (max-width: 640px) {
  .input-group {
    flex-direction: column;
    align-items: flex-start;
  }

  .input-group label {
    margin-bottom: 0.25rem;
  }

  .input-group input[type="number"] {
    width: 100%;
  }

  .result-box {
    flex-direction: column;
    align-items: flex-start;
    gap: 0.5rem;
  }

  #matchCanvas {
    width: 100%;
    height: auto;
  }
}

.simulation-container {
  margin-top: 1rem;
}

.simulation-header {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 0.75rem;
  margin-bottom: 1rem;
  padding: 0.75rem;
  background-color: var(--bg);
  border-radius: var(--standard-border-radius);
  border: 2px solid var(--ultra-violet);
}

.sim-stat {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.sim-label {
  font-size: 0.75rem;
  color: var(--text-light);
  font-weight: 600;
}

.sim-value {
  font-size: 1rem;
  color: var(--pink-puree);
  font-family: var(--mono-font);
  font-weight: 700;
}

#matchCanvas {
  width: 100%;
  height: 200px;
  background-color: var(--nightshade-violet);
  border-radius: var(--standard-border-radius);
  border: 2px solid var(--ultra-violet);
  display: block;
}

.timeline-container {
  margin-top: 1rem;
  position: relative;
}

.timeline-labels {
  display: flex;
  justify-content: space-between;
  margin-bottom: 0.5rem;
  font-size: 0.75rem;
  color: var(--text-light);
  padding: 0 0.5rem;
}

.timeline-scrubber {
  width: 100%;
  height: 8px;
  border-radius: 4px;
  background: var(--bg);
  border: 2px solid var(--ultra-violet);
  outline: none;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
}

.timeline-scrubber::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--rose-quartz);
  cursor: pointer;
  border: 2px solid var(--pink-puree);
}

.timeline-scrubber::-moz-range-thumb {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--rose-quartz);
  cursor: pointer;
  border: 2px solid var(--pink-puree);
}

.simulation-controls {
  display: flex;
  gap: 0.75rem;
  align-items: center;
  margin-top: 1rem;
  flex-wrap: wrap;
}

.sim-button {
  padding: 0.5rem 1rem;
  background-color: var(--accent);
  color: var(--accent-text);
  border: 2px solid var(--ultra-violet);
  border-radius: var(--standard-border-radius);
  font-weight: 600;
  cursor: pointer;
  transition: all 120ms ease;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}

.sim-button:hover {
  background-color: var(--rose-quartz);
  border-color: var(--pink-puree);
}

.sim-button:active {
  transform: translateY(1px);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
}

.speed-controls {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-left: auto;
}

.speed-controls label {
  font-size: 0.875rem;
  color: var(--text);
  font-weight: 600;
}

.speed-controls select {
  padding: 0.5rem;
  background-color: var(--bg);
  color: var(--text);
  border: 2px solid var(--ultra-violet);
  border-radius: var(--standard-border-radius);
  font-family: inherit;
  cursor: pointer;
}
</style>

<script>
(function() {
  // Wait for DOM to be ready
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', initCalculator);
  } else {
    initCalculator();
  }

  function initCalculator() {
    const calculator = document.getElementById("frc-calculator");
    if (!calculator) return;

    // Get all input elements
    const inputs = {
      ballCapacity: document.getElementById("ballCapacity"),
      reloadTime: document.getElementById("reloadTime"),
      shooterBPS: document.getElementById("shooterBPS"),
      numRobots: document.getElementById("numRobots"),
      graceTime: document.getElementById("graceTime"),
      includeAuto: document.getElementById("includeAuto"),
      autoShootTime: document.getElementById("autoShootTime"),
      includeEndgame: document.getElementById("includeEndgame"),
      endgameShootTime: document.getElementById("endgameShootTime"),
      wonAuto: document.getElementById("wonAuto"),
      energizedThreshold: document.getElementById("energizedThreshold"),
      superchargedThreshold: document.getElementById("superchargedThreshold"),
    };

    // Get all result elements
    const results = {
      totalTime: document.getElementById("totalTime"),
      cycleTime: document.getElementById("cycleTime"),
      energizedAllianceBPS: document.getElementById("energizedAllianceBPS"),
      energizedRobotBPS: document.getElementById("energizedRobotBPS"),
      superchargedAllianceBPS: document.getElementById("superchargedAllianceBPS"),
      superchargedRobotBPS: document.getElementById("superchargedRobotBPS"),
    };

    // Toggle visibility of conditional inputs
    const autoTimeGroup = document.getElementById("autoTimeGroup");
    const endgameTimeGroup = document.getElementById("endgameTimeGroup");

    inputs.includeAuto.addEventListener("change", () => {
      autoTimeGroup.style.display = inputs.includeAuto.checked ? "flex" : "none";
      calculate();
    });

    inputs.includeEndgame.addEventListener("change", () => {
      endgameTimeGroup.style.display = inputs.includeEndgame.checked ? "flex" : "none";
      calculate();
    });

    // Add event listeners to all inputs
    Object.values(inputs).forEach((input) => {
      input.addEventListener("input", calculate);
      input.addEventListener("change", calculate);
    });

    function calculate() {
      // Get input values
      const ballCapacity = parseFloat(inputs.ballCapacity.value) || 0;
      const reloadTime = parseFloat(inputs.reloadTime.value) || 0;
      const shooterBPS = parseFloat(inputs.shooterBPS.value) || 4;
      const numRobots = parseFloat(inputs.numRobots.value) || 1;
      const includeAuto = inputs.includeAuto.checked;
      const autoShootTime = parseFloat(inputs.autoShootTime.value) || 0;
      const includeEndgame = inputs.includeEndgame.checked;
      const endgameShootTime = parseFloat(inputs.endgameShootTime.value) || 0;
      const wonAuto = inputs.wonAuto.checked;
      const energizedThreshold = parseFloat(inputs.energizedThreshold.value) || 0;
      const superchargedThreshold = parseFloat(inputs.superchargedThreshold.value) || 0;

      // Calculate total active scoring time
      let totalTime = 0;

      // Auto period (if included)
      if (includeAuto) {
        totalTime += autoShootTime;
      }

      // Transition period (always active, now 10s)
      totalTime += 10;

      // Shifts - 2 active shifts of 25s each
      // If won auto: Shifts 2 & 4 are active
      // If lost auto: Shifts 1 & 3 are active
      totalTime += 50; // 2 shifts × 25s

      // Endgame period (if included, now 30s)
      if (includeEndgame) {
        totalTime += endgameShootTime;
      }

      // Calculate free reloads during inactive hub time
      // There are 58 seconds of inactive time (8s break + 2 shifts × 25s)
      // This is the ONLY time we can reload without losing active scoring time
      const inactiveTime = 58;
      const freeReloads = reloadTime > 0 ? Math.floor(inactiveTime / reloadTime) : 0;

      // BPS calculation accounting for reload time during auto/endgame
      function calculateBPSForThreshold(threshold) {
        if (totalTime <= 0 || threshold <= 0 || ballCapacity <= 0) return 0;

        // Calculate cycles needed to reach threshold
        const cyclesNeeded = Math.ceil(threshold / ballCapacity);
        const reloadsNeeded = Math.max(0, cyclesNeeded - 1);

        // Reloads during inactive time are free
        // Remaining reloads consume active time
        const paidReloads = Math.max(0, reloadsNeeded - freeReloads);

        // Available shooting time = total active time - paid reload time
        const shootingTime = totalTime - (paidReloads * reloadTime);

        // BPS = threshold / available shooting time
        return shootingTime > 0 ? threshold / shootingTime : 0;
      }
      
      // Calculate max balls achievable by simulating full match
      function calculateMaxBalls() {
        if (shooterBPS <= 0 || ballCapacity <= 0) return 0;
        
        // Run simulation to completion (t=168s) with current settings
        let totalScored = 0;
        let cyclePosition = 0; // 0 = ready to shoot, 1 = reloading
        let reloadProgress = 0;
        let ballsInHopper = Math.min(ballCapacity, 8); // Preload
        
        const includeAuto = inputs.includeAuto.checked;
        const autoShootTime = parseFloat(inputs.autoShootTime.value) || 0;
        const includeEndgame = inputs.includeEndgame.checked;
        const endgameShootTime = parseFloat(inputs.endgameShootTime.value) || 0;
        
        for (let periodIndex = 0; periodIndex < periods.length; periodIndex++) {
          const period = periods[periodIndex];
          
          // Skip BREAK period
          if (period.name === "BREAK") continue;
          
          // Check if hub is active for this period
          let periodActive = period.active;
          if (period.name === "S1" || period.name === "S3") {
            periodActive = !wonAuto;
          } else if (period.name === "S2" || period.name === "S4") {
            periodActive = wonAuto;
          }
          
          // Determine effective time and if shooting is allowed
          const timeInPeriod = period.end - period.start;
          let effectiveTimeInPeriod = timeInPeriod;
          let canShootInPeriod = true;
          
          if (period.name === "AUTO") {
            if (!includeAuto) {
              canShootInPeriod = false;
            } else {
              effectiveTimeInPeriod = Math.min(timeInPeriod, autoShootTime);
            }
          } else if (period.name === "END") {
            if (!includeEndgame) {
              canShootInPeriod = false;
            } else {
              effectiveTimeInPeriod = Math.min(timeInPeriod, endgameShootTime);
            }
          }
          
          let timeRemaining = effectiveTimeInPeriod;
          
          // Check grace period eligibility
          let allowGraceShoot = false;
          if (!periodActive && periodIndex > 0 && period.name !== "BREAK") {
            const prevPeriod = periods[periodIndex - 1];
            let prevActive = prevPeriod.active;
            if (prevPeriod.name === "S1" || prevPeriod.name === "S3") {
              prevActive = !wonAuto;
            } else if (prevPeriod.name === "S2" || prevPeriod.name === "S4") {
              prevActive = wonAuto;
            }
            if (prevActive) {
              allowGraceShoot = true;
            }
          }
          
          // Simulate this period
          let timeIntoPeriod = 0;
          while (timeRemaining > 0) {
            const canShootWithGrace = periodActive || (allowGraceShoot && timeIntoPeriod < graceTime);
            
            if (cyclePosition === 0) {
              // Ready to shoot
              if (canShootWithGrace && canShootInPeriod) {
                const ballsToShoot = ballsInHopper;
                const timeToShootAll = ballsToShoot / shooterBPS;
                const timeToShoot = Math.min(timeToShootAll, timeRemaining);
                
                const ballsActuallyShot = Math.floor(timeToShoot * shooterBPS);
                totalScored += ballsActuallyShot * numRobots;
                ballsInHopper -= ballsActuallyShot;
                timeRemaining -= timeToShoot;
                timeIntoPeriod += timeToShoot;
                
                if (ballsInHopper <= 0) {
                  ballsInHopper = 0;
                  cyclePosition = 1;
                  reloadProgress = 0;
                } else {
                  break; // Still have balls but out of time
                }
              } else {
                break; // Can't shoot, wait
              }
            } else if (cyclePosition === 1) {
              // Reloading
              const timeToReload = Math.min(reloadTime - reloadProgress, timeRemaining);
              timeRemaining -= timeToReload;
              timeIntoPeriod += timeToReload;
              reloadProgress += timeToReload;
              
              if (reloadProgress >= reloadTime) {
                ballsInHopper = ballCapacity;
                cyclePosition = 0;
                reloadProgress = 0;
              } else {
                break; // Still reloading
              }
            }
          }
        }
        
        return totalScored;
      }

      const energizedAllianceBPS = calculateBPSForThreshold(energizedThreshold);
      const superchargedAllianceBPS = calculateBPSForThreshold(superchargedThreshold);
      const maxBalls = calculateMaxBalls();

      // Calculate per robot BPS
      const energizedRobotBPS = numRobots > 0 ? energizedAllianceBPS / numRobots : 0;
      const superchargedRobotBPS = numRobots > 0 ? superchargedAllianceBPS / numRobots : 0;

      // Cycle time is the time to shoot all balls in the hopper plus reload time
      // Based on physical shooter speed (not target BPS)
      const shootingTimePerCycle = ballCapacity > 0 && shooterBPS > 0 ? ballCapacity / shooterBPS : 0;
      const avgCycleTime = shootingTimePerCycle + reloadTime;

      // Update results
      results.totalTime.textContent = `${totalTime.toFixed(1)}s`;
      results.cycleTime.textContent = avgCycleTime > 0 ? `${avgCycleTime.toFixed(2)}s` : "N/A";
      
      // Show BPS or "N/A (max X balls)" if unreachable
      if (energizedAllianceBPS > 0 && maxBalls >= energizedThreshold) {
        results.energizedAllianceBPS.textContent = `${energizedAllianceBPS.toFixed(2)} balls/s`;
        results.energizedRobotBPS.textContent = `${energizedRobotBPS.toFixed(2)} balls/s`;
      } else {
        results.energizedAllianceBPS.textContent = `N/A (max ${maxBalls} balls)`;
        results.energizedRobotBPS.textContent = `N/A (max ${maxBalls} balls)`;
      }
      
      if (superchargedAllianceBPS > 0 && maxBalls >= superchargedThreshold) {
        results.superchargedAllianceBPS.textContent = `${superchargedAllianceBPS.toFixed(2)} balls/s`;
        results.superchargedRobotBPS.textContent = `${superchargedRobotBPS.toFixed(2)} balls/s`;
      } else {
        results.superchargedAllianceBPS.textContent = `N/A (max ${maxBalls} balls)`;
        results.superchargedRobotBPS.textContent = `N/A (max ${maxBalls} balls)`;
      }
    }

    // Match Simulation
    const canvas = document.getElementById("matchCanvas");
    const ctx = canvas ? canvas.getContext("2d") : null;
    const timelineScrubber = document.getElementById("timelineScrubber");
    const playPauseBtn = document.getElementById("playPauseBtn");
    const resetBtn = document.getElementById("resetBtn");
    const speedSelect = document.getElementById("speedSelect");
    const simTime = document.getElementById("simTime");
    const simBalls = document.getElementById("simBalls");
    const simScored = document.getElementById("simScored");
    const simStatus = document.getElementById("simStatus");
    const timelineLabels = document.getElementById("timelineLabels");

    let isPlaying = false;
    let currentTime = 0;
    let animationFrame = null;
    let lastTimestamp = 0;

    // Match timeline periods
    const periods = [
      { name: "AUTO", start: 0, end: 20, active: true },
      { name: "BREAK", start: 20, end: 28, active: false },
      { name: "TRANS", start: 28, end: 38, active: true },
      { name: "S1", start: 38, end: 63, active: false },
      { name: "S2", start: 63, end: 88, active: true },
      { name: "S3", start: 88, end: 113, active: false },
      { name: "S4", start: 113, end: 138, active: true },
      { name: "END", start: 138, end: 168, active: true }
    ];

    function setupTimeline() {
      if (!timelineLabels) return;

      timelineLabels.innerHTML = periods.map(p => `<span>${p.name}</span>`).join('');
    }

    function drawCanvas() {
      if (!ctx || !canvas) return;

      const width = canvas.width;
      const height = canvas.height;
      const ballCapacity = parseFloat(inputs.ballCapacity.value) || 3;
      const reloadTime = parseFloat(inputs.reloadTime.value) || 5;
      const shooterBPS = parseFloat(inputs.shooterBPS.value) || 4;

      // Clear canvas
      ctx.fillStyle = '#1e1e2e'; // nightshade-violet
      ctx.fillRect(0, 0, width, height);

      // Determine current period
      const wonAuto = inputs.wonAuto.checked;
      let currentPeriod = periods.find(p => currentTime >= p.start && currentTime < p.end);
      if (!currentPeriod) currentPeriod = periods[periods.length - 1];

      // Draw timeline periods
      periods.forEach(period => {
        const x = (period.start / 168) * width;
        const w = ((period.end - period.start) / 168) * width;

        // Determine if hub is active based on wonAuto
        let isActive = period.active;

        if (period.name === "S1" || period.name === "S3") {
          isActive = !wonAuto;
        } else if (period.name === "S2" || period.name === "S4") {
          isActive = wonAuto;
        }

        // Check if this is the current period
        const isCurrent = period === currentPeriod;

        // Background color - brighter if current period
        if (isCurrent) {
          ctx.fillStyle = isActive ? 'rgba(166, 218, 149, 0.4)' : 'rgba(237, 135, 150, 0.4)';
        } else {
          ctx.fillStyle = isActive ? 'rgba(166, 218, 149, 0.15)' : 'rgba(237, 135, 150, 0.15)';
        }
        ctx.fillRect(x, 0, w, height);

        // Draw period border - thicker if current
        ctx.strokeStyle = isCurrent ? '#8aadf4' : 'rgba(138, 173, 244, 0.3)';
        ctx.lineWidth = isCurrent ? 2 : 1;
        ctx.strokeRect(x, 0, w, height);

        // Draw period label - bold if current
        ctx.fillStyle = '#cad3f5';
        ctx.font = isCurrent ? 'bold 14px monospace' : '12px monospace';
        ctx.textAlign = 'center';
        ctx.fillText(period.name, x + w / 2, isCurrent ? 22 : 20);
      });

      // Simulate robot state at current time
      let ballsInHopper = 0;
      let totalScored = 0;
      let status = "Idle";
      let timeInCycle = 0;
      const numRobots = Math.min(3, parseFloat(inputs.numRobots.value) || 1);
      const graceTime = parseFloat(inputs.graceTime.value) || 0;

      // Helper function to check if we're in grace period after hub goes inactive
      function isInGracePeriod(time) {
        // Find which period we're in
        const currentPeriodIndex = periods.findIndex(p => time >= p.start && time < p.end);
        if (currentPeriodIndex === -1) return false;
        
        const currentPer = periods[currentPeriodIndex];
        
        // Check if previous period was active and current is inactive
        if (currentPeriodIndex > 0) {
          const prevPeriod = periods[currentPeriodIndex - 1];
          
          let prevActive = prevPeriod.active;
          let currActive = currentPer.active;
          
          if (prevPeriod.name === "S1" || prevPeriod.name === "S3") {
            prevActive = !wonAuto;
          } else if (prevPeriod.name === "S2" || prevPeriod.name === "S4") {
            prevActive = wonAuto;
          }
          
          if (currentPer.name === "S1" || currentPer.name === "S3") {
            currActive = !wonAuto;
          } else if (currentPer.name === "S2" || currentPer.name === "S4") {
            currActive = wonAuto;
          }
          
          // If we went from active to inactive
          if (prevActive && !currActive) {
            const timeSinceTransition = time - currentPer.start;
            return timeSinceTransition <= graceTime;
          }
        }
        return false;
      }

      // Check if hub is active (including grace period)
      let isHubActive = currentPeriod.active;
      if (currentPeriod.name === "S1" || currentPeriod.name === "S3") {
        isHubActive = !wonAuto;
      } else if (currentPeriod.name === "S2" || currentPeriod.name === "S4") {
        isHubActive = wonAuto;
      }
      
      // Check grace period
      if (!isHubActive && isInGracePeriod(currentTime)) {
        isHubActive = true;
      }

      if (shooterBPS > 0) {
        const shootingTimePerCycle = ballCapacity / shooterBPS;
        const includeAuto = inputs.includeAuto.checked;
        const autoShootTime = parseFloat(inputs.autoShootTime.value) || 0;
        const includeEndgame = inputs.includeEndgame.checked;
        const endgameShootTime = parseFloat(inputs.endgameShootTime.value) || 0;

        // Simulate through all periods up to current time
        let timeAccumulator = 0;
        let cyclePosition = 0; // 0 = ready to shoot, 1 = reloading
        let reloadProgress = 0; // Track partial reload progress (0 to reloadTime)
        
        // Start with preloaded balls (max 8)
        ballsInHopper = Math.min(ballCapacity, 8);

        for (let periodIndex = 0; periodIndex < periods.length; periodIndex++) {
          const period = periods[periodIndex];
          if (currentTime < period.start) break;

          const periodEnd = Math.min(period.end, currentTime);
          const timeInPeriod = periodEnd - period.start;

          // Check if hub is active for this period
          let periodActive = period.active;
          if (period.name === "S1" || period.name === "S3") {
            periodActive = !wonAuto;
          } else if (period.name === "S2" || period.name === "S4") {
            periodActive = wonAuto;
          }

          // Limit time in period based on user settings for auto/endgame
          let effectiveTimeInPeriod = timeInPeriod;
          let canShootInPeriod = true;

          if (period.name === "AUTO") {
            if (!includeAuto) {
              canShootInPeriod = false;
            } else {
              // Limit to autoShootTime
              effectiveTimeInPeriod = Math.min(timeInPeriod, autoShootTime);
            }
          } else if (period.name === "END") {
            if (!includeEndgame) {
              canShootInPeriod = false;
            } else {
              // Limit to endgameShootTime
              effectiveTimeInPeriod = Math.min(timeInPeriod, endgameShootTime);
            }
          } else if (period.name === "BREAK") {
            // No activity during break - skip this period entirely
            if (currentTime >= period.start && currentTime <= periodEnd) {
              status = "Break";
            }
            continue; // Skip to next period
          }

          let timeRemaining = effectiveTimeInPeriod;
          
          // Check if we should allow shooting in grace period
          let allowGraceShoot = false;
          if (!periodActive && periodIndex > 0 && period.name !== "BREAK") {
            const prevPeriod = periods[periodIndex - 1];
            let prevActive = prevPeriod.active;
            if (prevPeriod.name === "S1" || prevPeriod.name === "S3") {
              prevActive = !wonAuto;
            } else if (prevPeriod.name === "S2" || prevPeriod.name === "S4") {
              prevActive = wonAuto;
            }
            
            // Previous period was active, current is inactive - allow grace period
            if (prevActive) {
              allowGraceShoot = true;
            }
          }

          while (timeRemaining > 0) {
            // Check if we're still in grace period
            const timeIntoPeriod = effectiveTimeInPeriod - timeRemaining;
            const canShootWithGrace = periodActive || (allowGraceShoot && timeIntoPeriod < graceTime);
            
            if (cyclePosition === 0) {
              // Ready to shoot (have balls loaded)
              if (canShootWithGrace && canShootInPeriod) {
                // Calculate how many balls we actually have to shoot
                const ballsToShoot = ballsInHopper;
                const timeToShootAll = ballsToShoot / shooterBPS;
                const timeToShoot = Math.min(timeToShootAll, timeRemaining);
                timeRemaining -= timeToShoot;

                const ballsActuallyShot = Math.floor(timeToShoot * shooterBPS);
                totalScored += ballsActuallyShot * numRobots;
                ballsInHopper -= ballsActuallyShot;

                if (ballsInHopper <= 0) {
                  // Finished shooting all balls - start reload
                  ballsInHopper = 0;
                  cyclePosition = 1;
                  reloadProgress = 0;

                  if (currentTime >= period.start && currentTime <= periodEnd && timeRemaining === 0) {
                    status = "Reloading";
                  }
                } else {
                  // Still shooting
                  if (currentTime >= period.start && currentTime <= periodEnd && timeRemaining === 0) {
                    status = "Shooting";
                  }
                  break;
                }
              } else {
                // Hub inactive, can't shoot - maintain current balls
                if (currentTime >= period.start && currentTime <= periodEnd) {
                  status = allowGraceShoot && timeIntoPeriod < graceTime ? "Grace Period" : "Idle";
                }
                break;
              }
            } else if (cyclePosition === 1) {
              // Reloading
              const timeToReload = Math.min(reloadTime - reloadProgress, timeRemaining);
              timeRemaining -= timeToReload;
              reloadProgress += timeToReload;

              if (reloadProgress >= reloadTime) {
                // Finished reloading
                ballsInHopper = ballCapacity;
                cyclePosition = 0;
                reloadProgress = 0;

                if (currentTime >= period.start && currentTime <= periodEnd && timeRemaining === 0) {
                  status = canShootWithGrace ? "Ready" : "Idle";
                }
              } else {
                // Still reloading - show partial progress
                if (currentTime >= period.start && currentTime <= periodEnd && timeRemaining === 0) {
                  const reloadPercent = reloadProgress / reloadTime;
                  ballsInHopper = Math.floor(ballCapacity * reloadPercent);
                  status = "Reloading";
                }
                break;
              }
            }
          }

          // Check if we're past the shooting time in auto/endgame
          if (currentTime >= period.start && currentTime <= periodEnd) {
            if (period.name === "AUTO" && includeAuto) {
              const timeIntoPeriod = currentTime - period.start;
              if (timeIntoPeriod >= autoShootTime) {
                status = "Idle";
                // Keep current balls loaded
              }
            } else if (period.name === "END" && includeEndgame) {
              const timeIntoPeriod = currentTime - period.start;
              if (timeIntoPeriod >= endgameShootTime) {
                status = "Idle";
                // Keep current balls loaded
              }
            }
          }
        }
      }

      // Draw progress bar
      const progressX = (currentTime / 168) * width;
      ctx.strokeStyle = '#f5bde6';
      ctx.lineWidth = 3;
      ctx.beginPath();
      ctx.moveTo(progressX, 0);
      ctx.lineTo(progressX, height);
      ctx.stroke();

      // Draw robot visualizations (one per robot, max 3)
      const robotWidth = 50;
      const robotHeight = 40;
      const robotSpacing = 80;
      const startX = 40;
      const robotY = height - 60;

      for (let robotIndex = 0; robotIndex < numRobots; robotIndex++) {
        const robotX = startX + (robotIndex * robotSpacing);

        // Robot body - color based on status
        let robotColor;
        if (status === "Shooting") {
          robotColor = '#a6da95'; // Green - shooting
        } else if (status === "Reloading") {
          robotColor = '#eed49f'; // Yellow - reloading
        } else {
          robotColor = '#ed8796'; // Red - idle/waiting
        }

        ctx.fillStyle = robotColor;
        ctx.fillRect(robotX, robotY, robotWidth, robotHeight);
        ctx.strokeStyle = '#8aadf4';
        ctx.lineWidth = 2;
        ctx.strokeRect(robotX, robotY, robotWidth, robotHeight);

        // Draw balls in hopper
        if (status === "Reloading") {
          // During reload, show balls filling up from bottom to top
          // ballsInHopper already contains the partial reload count
          if (ballCapacity <= 16) {
            const ballRadius = 4;
            const ballsPerRow = 4;
            for (let i = 0; i < ballsInHopper; i++) {
              const row = Math.floor(i / ballsPerRow);
              const col = i % ballsPerRow;
              const ballX = robotX + 6 + col * 10;
              const ballY = robotY + robotHeight - 6 - row * 10;

              ctx.fillStyle = '#f5a97f';
              ctx.beginPath();
              ctx.arc(ballX, ballY, ballRadius, 0, Math.PI * 2);
              ctx.fill();
            }
          } else {
            // For large capacities, show count and progress
            const reloadProgress = ballsInHopper / ballCapacity;
            ctx.fillStyle = '#f5a97f';
            ctx.font = 'bold 20px monospace';
            ctx.textAlign = 'center';
            ctx.fillText(ballsInHopper, robotX + 25, robotY + 25);
            
            // Progress bar
            ctx.fillStyle = 'rgba(245, 169, 127, 0.3)';
            ctx.fillRect(robotX + 5, robotY + robotHeight - 8, robotWidth - 10, 4);
            ctx.fillStyle = '#f5a97f';
            ctx.fillRect(robotX + 5, robotY + robotHeight - 8, (robotWidth - 10) * reloadProgress, 4);
          }
        } else if (ballCapacity <= 16) {
          // Draw individual balls for small capacities (up to 16)
          const ballRadius = 4;
          const ballsPerRow = 4;
          for (let i = 0; i < ballsInHopper; i++) {
            const row = Math.floor(i / ballsPerRow);
            const col = i % ballsPerRow;
            const ballX = robotX + 6 + col * 10;
            const ballY = robotY + 6 + row * 10;

            ctx.fillStyle = '#f5a97f';
            ctx.beginPath();
            ctx.arc(ballX, ballY, ballRadius, 0, Math.PI * 2);
            ctx.fill();
          }
        } else {
          // For large capacities, just show the count
          ctx.fillStyle = '#f5a97f';
          ctx.font = 'bold 24px monospace';
          ctx.textAlign = 'center';
          ctx.fillText(ballsInHopper, robotX + 25, robotY + 28);
        }

        // Robot number label
        ctx.fillStyle = '#8aadf4';
        ctx.font = 'bold 10px monospace';
        ctx.textAlign = 'center';
        ctx.fillText(`R${robotIndex + 1}`, robotX + 25, robotY - 4);
      }

      // Status text (shared for all robots)
      const statusX = startX + (numRobots * robotSpacing);
      ctx.fillStyle = '#cad3f5';
      ctx.font = 'bold 14px monospace';
      ctx.textAlign = 'left';
      ctx.fillText(status, statusX, robotY + 20);
      ctx.fillText(`Balls: ${ballsInHopper}/${ballCapacity}`, statusX, robotY + 40);

      // Update stats
      // Timer display logic: countdown in AUTO, pause at 0 during BREAK, count up in teleop
      // Match time is 160s (game time) but simulation runs 168s (includes 8s break)
      let displayTime;
      let matchTime; // Actual match time shown to drivers
      
      if (currentTime <= 20) {
        // AUTO period: countdown from 15 to 0
        matchTime = 20 - currentTime;
        displayTime = matchTime.toFixed(1);
      } else if (currentTime <= 28) {
        // BREAK period: stays at 0 (doesn't count toward match time)
        matchTime = 0;
        displayTime = "0.0";
      } else {
        // Teleop: count up from 0 to 140 (subtract the 8s break)
        matchTime = currentTime - 8; // Remove the break time
        displayTime = (matchTime - 20).toFixed(1); // Show time since auto ended
      }
      
      if (simTime) simTime.textContent = `${displayTime}s`;
      if (simBalls) simBalls.textContent = ballsInHopper;
      if (simScored) simScored.textContent = totalScored;
      if (simStatus) simStatus.textContent = status;
    }

    function animate(timestamp) {
      if (!isPlaying) return;

      if (lastTimestamp === 0) lastTimestamp = timestamp;
      const deltaTime = (timestamp - lastTimestamp) / 1000; // Convert to seconds
      lastTimestamp = timestamp;

      const speed = parseFloat(speedSelect.value) || 1;
      currentTime += deltaTime * speed;

      if (currentTime >= 168) {
        currentTime = 168;
        pause();
      }

      if (timelineScrubber) timelineScrubber.value = currentTime;
      drawCanvas();

      if (isPlaying) {
        animationFrame = requestAnimationFrame(animate);
      }
    }

    function play() {
      if (currentTime >= 160) currentTime = 0;
      isPlaying = true;
      lastTimestamp = 0;
      if (playPauseBtn) playPauseBtn.textContent = "Pause";
      animationFrame = requestAnimationFrame(animate);
    }

    function pause() {
      isPlaying = false;
      if (playPauseBtn) playPauseBtn.textContent = "Play";
      if (animationFrame) cancelAnimationFrame(animationFrame);
    }

    function reset() {
      pause();
      currentTime = 0;
      if (timelineScrubber) timelineScrubber.value = 0;
      drawCanvas();
    }

    // Event listeners
    if (playPauseBtn) {
      playPauseBtn.addEventListener("click", () => {
        if (isPlaying) pause();
        else play();
      });
    }

    if (resetBtn) {
      resetBtn.addEventListener("click", reset);
    }

    if (timelineScrubber) {
      timelineScrubber.addEventListener("input", (e) => {
        currentTime = parseFloat(e.target.value);
        drawCanvas();
      });

      timelineScrubber.addEventListener("mousedown", () => {
        if (isPlaying) pause();
      });
    }

    // Redraw on parameter changes
    Object.values(inputs).forEach((input) => {
      input.addEventListener("input", () => {
        if (!isPlaying) drawCanvas();
      });
    });

    // Initialize
    setupTimeline();
    drawCanvas();

    // Initial calculation
    calculate();
  }
})();
</script>
<p>Hopefully this can help your team! May your BPS be ever optimal.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Homelab tour</title>
      <link>https://dunkirk.sh/blog/homelab-tour/</link>
      <guid isPermaLink="true">https://dunkirk.sh/blog/homelab-tour/</guid>
      <pubDate>Thu, 18 Dec 2025 00:00:00 +0000</pubDate>
      
      
      <dc:creator>Kieran Klukas</dc:creator>
      
      
        
      <category>homelab</category>
        
      
      <description>
        
          &amp;lt;p&amp;gt;Well this is a post I somehow have procrastinated on. I originally got the idea to write this up for the &amp;lt;a rel=&amp;quot;noopener external&amp;quot; target=&amp;quot;_blank&amp;quot; href=&amp;quot;https:&#x2F;&#x2F;linuxunplugged.com&#x2F;646&amp;quot;&amp;gt;LUP holiday homelab special&amp;lt;&#x2F;a&amp;gt; but before I knew it the submission due date was upon me and I hadn’t started so here goes a speedrun tour of my lab.&amp;lt;&#x2F;p&amp;gt;
        
      </description>
      <content:encoded><![CDATA[<p>Well this is a post I somehow have procrastinated on. I originally got the idea to write this up for the <a rel="noopener external" target="_blank" href="https://linuxunplugged.com/646">LUP holiday homelab special</a> but before I knew it the submission due date was upon me and I hadn’t started so here goes a speedrun tour of my lab.</p>
<span id="continue-reading"></span><figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;6PXIFV4xl2Ye.webp" alt="me with my trusty server"  />
    </div>
    
    <figcaption><p>This is my server ember, say hi!</p>
</figcaption>
    
</figure>
<p>I have a few main machines. In order of when I first got them they are:</p>
<ol>
<li>broylt (~2021 main pc / gpu machine :windows:)</li>
<li>thespia (~2022 first homelab workstation; runs a vm named vault which houses my storage :proxmox:)</li>
<li>ember (~2024 first “real” server; dell poweredge r210 :ubuntu:)</li>
<li>nest (early 2024 shared tilde server @ Hackclub :nix:)</li>
<li>moonlark (late 2024 framework 13; was my main laptop but the mainboard fried itself and I haven’t fixed it yet :nix:)</li>
<li>tacyon / pihole (summer 2025 rpi 5 home manager cyber pi / pihole :raspberry_pi:)</li>
<li>atalanta (late 2025 macbook air m4 16gb main computing device and what I’m typing this on rn :mac:)</li>
<li>terebithia (late 2025 oracle cloud free tier arm vm with 24gb ram and 150 gb of storage :nix:)</li>
<li>prattle (late 2025 oracle cloud amd vm that has since died but it used to be my monitoring node :nix:)</li>
</ol>
<p>As many of my machines as I can are running :nix: in some flavor. <code>moonlark</code> and <code>terebithia</code> are the only ones running nixos at the moment but I’m planning on switching <code>ember</code> over soon as well as maybe resurrecting <code>prattle</code> as well. All my other machines minus thespia and broylt are running my <a rel="noopener external" target="_blank" href="https://tangled.org/dunkirk.sh/dots">dots</a> via home manager.</p>
<h3 id="ember">Ember</h3>
<figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;6z4tVvS9eGt2.webp" alt="neofetch on ember"  />
    </div>
    
</figure>
<p>This used to run all of my services up till a month ago when I setup my oracle cloud instance and switched all of my main hosted services over. It currently runs my jellyfin / arr stack and any random workloads I want to throw somewhere without setting up a nix config for it. It in the very near future is probably going to become a quick deploy server in lieu of railway and similar services. The plan is to have a simple cli that will just throw everything on the server from a local repo and just run it with a domain.</p>
<p>For jellyfin the best way I have found to interact with it is via <code>Ruddar</code> on my phone and <code>Infuse</code> on either my phone or apple tv. It works insanely well for streaming stuff and I’m very tempted to buy a life time sub but I just don’t use my media library enough for it to be justified.</p>
<h3 id="terebithia">Terebithia</h3>
<figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;5qwQ7HP0nm4e.webp" alt="neofetch on terebithia"  />
    </div>
    
</figure>
<p>This has really been a rock solid machine for me. I have it setup with my nix config and it auto deploys with <code>deploy-rs</code> from github actions over tailscale. It is running my <a rel="noopener external" target="_blank" href="https://tangled.org/@dunkirk.sh">tangled.org</a> knot so it hosts all of my git repos and I have a fancy little automation that sets up git hooks in the repos as they are created to auto mirror to github. It is also running <a rel="noopener external" target="_blank" href="https://cachet.dunkirk.sh">cachet</a> which is a slack profile picture and emoji proxy (thats where all the emojis in this blog are pulled from) that I built to work at extremely high scale. It was running on <code>ember</code> for a while but I swapped it over to here to be a bit more reliable since so many people in Hackclub rely on it now. I’m also running a few slack bots and <a rel="noopener external" target="_blank" href="https://battle.dunkirk.sh">battleship-arena</a> again through their own nix services.</p>
<figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;TTbQriehUJDb.webp" alt="bore screenshot"  />
    </div>
    
    <figcaption><p>bore in all it’s glory</p>
</figcaption>
    
</figure>
<p>My most exciting services hosted on here though are <a rel="noopener external" target="_blank" href="https://bore.dunkirk.sh">bore</a> and <a rel="noopener external" target="_blank" href="https://indiko.dunkirk.sh/docs">indiko</a>. Bore is a little wrapper I made around <a rel="noopener external" target="_blank" href="https://github.com/fatedier/frp">frp</a> which allows me to easily deploy and view my tunnels. It is essentially an ngrok replacement and super slick to use. I made a custom cli for it with <code>gum</code> and nix which you can find along with setup instructions on the <a rel="noopener external" target="_blank" href="https://tangled.org/dunkirk.sh/dots/tree/main/modules/nixos/services/bore">tangled repo</a>.</p>
<figure class="center" >
    <div class="img-group" data-images="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;K-GWKS8Lh-CQ.webp, https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;N8I51AOs-va7.webp" data-alts="oauth screenshot, users managment">
    
    
    
        <div class="img-container" data-lightbox data-lightbox-group="group-https-l4-dunkirk-sh-i-k-gwks8lh-cq-webp-https-l4-dunkirk-sh-i-n8i51aos-va7-webp">
            <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;K-GWKS8Lh-CQ.webp" alt="oauth screenshot"  />
        </div>
    
        <div class="img-container" data-lightbox data-lightbox-group="group-https-l4-dunkirk-sh-i-k-gwks8lh-cq-webp-https-l4-dunkirk-sh-i-n8i51aos-va7-webp">
            <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;N8I51AOs-va7.webp" alt="users managment"  />
        </div>
    
    </div>
    
    <figcaption><p>the indiko admin ui and oauth consent screen</p>
</figcaption>
    
</figure>
<p>My newest project is <a rel="noopener external" target="_blank" href="https://tangled.org/dunkirk.sh/indiko/">Indiko</a>. It is a selfhosted IndieAuth / OAuth 2.0 compatible auth server somewhat like Authelia or Authentik but mine! I can defined custom clients and then use it to authorize with my own apps. I’m planning to add support to bore for using indiko as an authentication middleware on protected tunnels probably tomorrow. I’m currently using this to authenticate my shortlinks service <a rel="noopener external" target="_blank" href="https://hop.dunkirk.sh">hop</a> (<a rel="noopener external" target="_blank" href="https://tangled.org/dunkirk.sh/hop">repo</a>) which allows me to add new viewers and admins on the fly!</p>
<p>There is still a ton of head room on this server so I’m looking forward to adding quite a bit more here.</p>
<h4 id="the-nix-services-stack">The nix services stack</h4>
<p>Right now I have a pretty sweet stack for how I deploy my apps with nix. I have a caddy setup that is connected to my cloudflare account for auto generating certifications via dns attestation and then because it’s nix I can just add a new caddy block and it adds it onto my config file.</p>
<p>The way it works is I have service modules in <code>modules/nixos/services/</code> that follow a pretty consistent pattern.
Each one has options for the domain, port, and secrets file. When you enable a service it creates a system user, sets up passwordless sudo for restarting the systemd service (needed for non interactive CI/CD), and then runs the app with whatever command is needed. The cool part is in the preStart where it git clones the repo if it doesn’t exist and then optionally pulls on restart so I can just push to github and restart the service to deploy. Most of the time though I set up a workflow with tailscale to ssh in and pull down the new content and then restart. I have finally started using proper acls and tags so I feel very proud of myself.</p>
<p>Each service automatically configures its own caddy virtualHost with cloudflare dns challenge for TLS. So when I add a
new service I just do:</p>
<pre class="giallo z-code"><code data-lang="nix"><span class="giallo-l"><span class="z-punctuation">{</span></span>
<span class="giallo-l"><span class="z-entity z-other z-attribute-name z-multipart z-nix">    atelier</span><span class="z-source">.</span><span class="z-entity z-other z-attribute-name z-multipart z-nix">services</span><span class="z-source">.</span><span class="z-entity z-other z-attribute-name z-multipart z-nix">cachet</span><span class="z-keyword z-operator"> =</span><span class="z-punctuation"> {</span></span>
<span class="giallo-l"><span class="z-entity z-other z-attribute-name z-multipart z-nix">        enable</span><span class="z-keyword z-operator"> =</span><span class="z-constant z-language"> true</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-entity z-other z-attribute-name z-multipart z-nix">        domain</span><span class="z-keyword z-operator"> =</span><span class="z-punctuation z-definition z-string z-string"> &quot;cachet.dunkirk.sh&quot;</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-entity z-other z-attribute-name z-multipart z-nix">        secretsFile</span><span class="z-keyword z-operator"> =</span><span class="z-variable z-parameter z-name z-nix"> config</span><span class="z-keyword z-operator">.</span><span class="z-variable z-parameter z-name z-nix">age</span><span class="z-keyword z-operator">.</span><span class="z-variable z-parameter z-name z-nix">secrets</span><span class="z-keyword z-operator">.</span><span class="z-variable z-parameter z-name z-nix">cachet</span><span class="z-keyword z-operator">.</span><span class="z-variable z-parameter z-name z-nix">path</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-punctuation">    };</span></span>
<span class="giallo-l"><span class="z-punctuation">}</span></span></code></pre>
<p>And boom - it clones the repo, installs dependencies, starts the systemd service, and sets up caddy with automatic
HTTPS. All the secrets are managed with agenix so they’re encrypted with my ssh key and decrypted at boot. The
cloudflare API token gets injected into caddy’s environment so the DNS challenge just works.</p>
<p>For more complex services like indiko I can add rate limiting in the caddy config – I have different rate
limits for <code>/auth/_</code>, <code>/api/_</code>, and general routes all configured in the service module itself. It’s all declarative
so the entire stack (caddy, TLS, DNS, users, app deployment) is just nix config that can be auto deployed via deploy-rs from
github actions over tailscale.</p>
<h3 id="thespia">Thespia</h3>
<figure class="5" >
    <div class="img-group" data-images="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;PRp924X_rorV.webp, https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;tBkhMjYzfH-K.webp" data-alts="the harddrives falling out of the case, harddrives in the front">
    
    
    
        <div class="img-container" data-lightbox data-lightbox-group="group-https-l4-dunkirk-sh-i-prp924x-rorv-webp-https-l4-dunkirk-sh-i-tbkhmjyzfh-k-webp">
            <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;PRp924X_rorV.webp" alt="the harddrives falling out of the case"  />
        </div>
    
        <div class="img-container" data-lightbox data-lightbox-group="group-https-l4-dunkirk-sh-i-prp924x-rorv-webp-https-l4-dunkirk-sh-i-tbkhmjyzfh-k-webp">
            <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;tBkhMjYzfH-K.webp" alt="harddrives in the front"  />
        </div>
    
    </div>
    
    <figcaption><p>I have been blessed with ~5tb of HDD storage but I currently have no place to put it so this is only about 1.5 TB and it is truely attrocious</p>
</figcaption>
    
</figure>
<p>This is the most jank part of the whole lab. It is one of my oldest dedicated machines and it is kind of showing it’s age. I really don’t do much with it anymore but the harddrives are still somehow kicking. I inherited a large cardboard box of drives from my great uncle (I also got ember from him) and they all have about 10-15% of life left but are in 500 GB and 750 GB sizes which is a wee bit annoying. I’m most likely going to get a beelink or something else that is small and power efficent and just shove a ridiculous amount of m.2 SSDs in there.</p>
<p>This used to my my pride and joy and I paired it with an old laptop (laptops make shockingly good budget servers btw; they have essentially a built in ups after all and are quite power efficent at times) which was named <code>thalia</code>. Between those two I made a multi-node proxmox cluster and it was amazing.</p>
<h3 id="broylt">Broylt</h3>
<p>This is my first PC built from back when I was 13. I got some of the parts for my birthday and saved up to buy the rest and it was glorious when I first built it. Honestly it hasn’t changed a ton since back then. I swapped out the power supply as the original one kept failing and I had to RMA it. I also finally swapped the GPU to a 2070 Super a year or two ago which has been amazing. It is still rocking the original SSD, Asus B550M Plus Wifi motherboard, and the ryzen 5 3600 CPU and honestly I have very little complaints expect for the fact it is windows and that it keeps randomly crashing if I do anything too intense with the GPU. Honestly the issue is probably just that it needs a bigger PSU and as for windows thats really the only reason I still keep it around as you really still do need a windows box for running odd jobs and perhaps most importantly the occasional valorant session.</p>
<h3 id="atalanta">Atalanta</h3>
<p>This is my macbook which I got this fall after my framework died. It definitly is a wee bit more reliable than my hacky hyprland config but I do still miss my framework. I will probably end up reviving it soon with a new mainboard and it will become my dual boot nixos / windows competition laptop for both FRC (robotics) and various cyber competitions.</p>
<figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;vhs.charm.sh&#x2F;vhs-1KyplPxIt9f1kNmruDVPVS.gif" alt="shell config"  />
    </div>
    
</figure>
<p>As far as special stuff on this machine there isn’t a ton to tell. I’m doing some fancy configuration with nix darwin to make the default screenshot action copy to clipboard and I’m completely removing all pinned items from the doc but I really could be doing more (thanks to <a rel="noopener external" target="_blank" href="https://github.com/nickwelsh/nix-darwin-config">Nick Welsh</a> for helping me realize this was possible last config confessions on LUP). I do have my custom shell config which I have been refining for the better part of a year and a half now. It was originally inspired by the <a rel="noopener external" target="_blank" href="https://www.youtube.com/@dreamsofcode">Dreams of Code</a> youtube channel zen shell config and then it has become heavily adapted over time. I really like how minimal and clean it feels. If I’m in a git repo it will adapt to display commit status and push / pull status and if I’m over ssh or in a zmx session it also adapts.</p>
<p><a rel="noopener external" target="_blank" href="https://zmx.sh/">Zmx</a> is another one of the fancy things I have started using. It is terminal sessions like tmux but without the bloat. It is a super small program but it does pretty much exactly what I want. I have my servers set up so that I can ssh into them with <code>ssh t.*</code> and it will use that session. It is super slick for long running processes.</p>
<h3 id="network-stack">Network stack</h3>
<figure class="center" >
    <div class="img-group" data-images="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;JC13Aar6VPg2.webp, https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;PYMlcqfBHHnD.webp, https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;gygT3oV7R8LH.webp" data-alts="router and switch, the access point, rpi">
    
    
    
        <div class="img-container" data-lightbox data-lightbox-group="group-https-l4-dunkirk-sh-i-jc13aar6vpg2-webp-https-l4-dunkirk-sh-i-pymlcqfbhhnd-webp-https-l4-dunkirk-sh-i-gygt3ov7r8lh-webp">
            <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;JC13Aar6VPg2.webp" alt="router and switch"  />
        </div>
    
        <div class="img-container" data-lightbox data-lightbox-group="group-https-l4-dunkirk-sh-i-jc13aar6vpg2-webp-https-l4-dunkirk-sh-i-pymlcqfbhhnd-webp-https-l4-dunkirk-sh-i-gygt3ov7r8lh-webp">
            <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;PYMlcqfBHHnD.webp" alt="the access point"  />
        </div>
    
        <div class="img-container" data-lightbox data-lightbox-group="group-https-l4-dunkirk-sh-i-jc13aar6vpg2-webp-https-l4-dunkirk-sh-i-pymlcqfbhhnd-webp-https-l4-dunkirk-sh-i-gygt3ov7r8lh-webp">
            <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;gygT3oV7R8LH.webp" alt="rpi"  />
        </div>
    
    </div>
    
    <figcaption><p>it is fairly basic but it does work</p>
</figcaption>
    
</figure>
<p>Everything is based on tplink stuff which I rather hate. Their access points are fine but the router and switch are despicable to work with. Their app is even worse if that is even possible. I really love my old setup of using an old workstation to run pfSense with two NICs. It was clean and reliable and the ui was okay to work with. I had to switch it over for my parents so that ostensibly it would be simpler and easier for them to use which as time has proven was very much not true. If money was no object I would love to get a founders edition <a rel="noopener external" target="_blank" href="https://mono.si/">Gateway</a> router. You can really tell that it has had heart and soul poured into it and it looks soooooo good as a result.</p>
<p>As far as routing and port forwarding go I generally try to avoid it as much as humanly possible because of the atrocity of the router ui and as a result I have no ports open at the moment. I used cloudflare tunnels for practically everything at this point though now that I have my own VPS I might start using caddy to tunnel stuff back over tailscale for me instead.</p>
<h3 id="nest">Nest</h3>
<figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;bO5ra0U14_Vh.webp" alt="my static site on nest"  />
    </div>
    
    <figcaption><p>rendered from my github readme and served by caddy on nest</p>
</figcaption>
    
</figure>
<p>This is where I used to host most of my throwaway services and slackbots. I still do host a fair amount here but it has got increasingly slower as the user count just keeps going up. It definetly isn’t the fault of the admins though. I’m friends with almost all of them and they have done a wonderful job trying to keep it online while it tries to explode itself constantly. It has quite litterally been upgraded at least 4 times now with more and more compute and storage and we keep running out.</p>
<p>Fully nixos server so there is that :)</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Novel phishing tactic using github notifications</title>
      <link>https://dunkirk.sh/blog/github-phishing/</link>
      <guid isPermaLink="true">https://dunkirk.sh/blog/github-phishing/</guid>
      <pubDate>Fri, 24 Oct 2025 00:00:00 +0000</pubDate>
      
      
      <dc:creator>Kieran Klukas</dc:creator>
      
      
        
      <category>phishing</category>
        
      
      <description>
        
          &amp;lt;p&amp;gt;I received an email yesterday at &amp;lt;code&amp;gt;19:45 EST&amp;lt;&#x2F;code&amp;gt; titled &amp;lt;code&amp;gt;[yccombinator&#x2F;-notification] Y-Combinator W2026 | $15M Y-Combinator &amp;amp;amp; GitHub (Issue #126)&amp;lt;&#x2F;code&amp;gt;. From a quick glance it was easy to tell that it was a phising email funneling people to &amp;lt;code&amp;gt;https:&#x2F;&#x2F;y-comblnator.com&#x2F;apply&amp;lt;&#x2F;code&amp;gt;. They did at least try to disguise the link but then there is a ton of whitespace and you can see that they tagged 32 github users including mine.&amp;lt;&#x2F;p&amp;gt;
        
      </description>
      <content:encoded><![CDATA[<p>I received an email yesterday at <code>19:45 EST</code> titled <code>[yccombinator/-notification] Y-Combinator W2026 | $15M Y-Combinator &amp; GitHub (Issue #126)</code>. From a quick glance it was easy to tell that it was a phising email funneling people to <code>https://y-comblnator.com/apply</code>. They did at least try to disguise the link but then there is a ton of whitespace and you can see that they tagged 32 github users including mine.</p>
<span id="continue-reading"></span><figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;hc-cdn.hel1.your-objectstorage.com&#x2F;s&#x2F;v3&#x2F;47a842d35a86d6ac16d717b40ee69f2f801ff852_screenshot_2025-09-23_at_21.23.19.png" alt="a screenshot of the email"  />
    </div>
    
    <figcaption><p>I’ve never seen something simultaniously this stupid and (as far as i can tell) novel</p>
</figcaption>
    
</figure>
<p>Like most phishing emails I doubt most people would fall for this but if you were moving quickly and not thinking straight maybe you could fall for this?</p>
<p>Cloudflare has blocked the site due to phishing by now (13:17 Sept 24th) which is a shame since I would have loved to dig into the site a bit.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Musings about Atuin</title>
      <link>https://dunkirk.sh/blog/atuin/</link>
      <guid isPermaLink="true">https://dunkirk.sh/blog/atuin/</guid>
      <pubDate>Thu, 24 Apr 2025 00:00:00 +0000</pubDate>
      
      
      <dc:creator>Kieran Klukas</dc:creator>
      
      
        
      <category>shell</category>
        
      <category>nix</category>
        
      <category>cool stuff</category>
        
      
      <description>
        
          &amp;lt;p&amp;gt;I’ve been on the fence about using &amp;lt;a rel=&amp;quot;noopener external&amp;quot; target=&amp;quot;_blank&amp;quot; href=&amp;quot;https:&#x2F;&#x2F;atuin.sh&amp;quot;&amp;gt;Atuin&amp;lt;&#x2F;a&amp;gt; for about a month now. I heard about it from &amp;lt;a rel=&amp;quot;noopener external&amp;quot; target=&amp;quot;_blank&amp;quot; href=&amp;quot;https:&#x2F;&#x2F;ellie.wtf&amp;quot;&amp;gt;Ellie&amp;lt;&#x2F;a&amp;gt; on bluesky and initially didn’t bother setting it up since I didn’t really care about whether my shell history was synced across devices as I’m only using one main device (framework 13 🔥) rn. I saw a repost of Ellie’s about Atuin Desktop today and that finally pushed me over the edge to take the time to figure out how to get it setup with nix.&amp;lt;&#x2F;p&amp;gt;
        
      </description>
      <content:encoded><![CDATA[<p>I’ve been on the fence about using <a rel="noopener external" target="_blank" href="https://atuin.sh">Atuin</a> for about a month now. I heard about it from <a rel="noopener external" target="_blank" href="https://ellie.wtf">Ellie</a> on bluesky and initially didn’t bother setting it up since I didn’t really care about whether my shell history was synced across devices as I’m only using one main device (framework 13 🔥) rn. I saw a repost of Ellie’s about Atuin Desktop today and that finally pushed me over the edge to take the time to figure out how to get it setup with nix.</p>
<span id="continue-reading"></span>           
<blockquote>
    A lot of my infra workflows live in Slack threads, docs, or buried in shell history. That sucked.

I&#x27;ve been building Atuin Desktop. Local-first, CRDT-powered, executable runbooks - with integrated terminals, sql queries + monitoring

blog.atuin.sh&#x2F;atuin-deskto...

Lmk if you have any questions &lt;3  
    <video controls poster="https:&#x2F;&#x2F;video.bsky.app&#x2F;watch&#x2F;did%3Aplc%3A6pz6ncxxtia36hrtbf24wzue&#x2F;bafkreibusjd4b6gy4zhirxcct34rpowkziuhqmfwou4gsi3oeu5ezcqzaq&#x2F;thumbnail.jpg">
        <source
            src="https:&#x2F;&#x2F;video.bsky.app&#x2F;watch&#x2F;did%3Aplc%3A6pz6ncxxtia36hrtbf24wzue&#x2F;bafkreibusjd4b6gy4zhirxcct34rpowkziuhqmfwou4gsi3oeu5ezcqzaq&#x2F;playlist.m3u8"
            type="application/x-mpegURL"
        />
    </video>
     
</blockquote>
<p>
    <cite>
        <a href="https:&#x2F;&#x2F;bsky.app&#x2F;profile&#x2F;ellie.wtf&#x2F;post&#x2F;3lng5ig2o722z" target="_blank" rel="noopener"
            ><img
                src="https:&#x2F;&#x2F;cdn.bsky.app&#x2F;img&#x2F;avatar&#x2F;plain&#x2F;did:plc:6pz6ncxxtia36hrtbf24wzue&#x2F;bafkreibdwwckoua2rcyottpakswr6dx2pad3jmipw3uo3e2ms434nvtmkm"
                alt="Ellie Huxtable's avatar"
                class="avatar"
            />@ellie.wtf</a
        ></cite
    >
</p>

<p>And it wasn’t that hard! Atuin is published on nixpkgs or can be installed via a flake and there is a home manager module for it too! Once you get past actual installation and into using agenix to declaritively manage your secrets then it gets annoying (proly mainly because i’m still pretty stupid when it comes to nix lol).</p>
<p>The first bit is that to access age secrets in home manager you have to actually export them in home manager (🤯) and you can’t just use the version from your <code>configuration.nix</code>. The second bit is that you <strong>also</strong> need to export the age file in your <code>configuration.nix</code> (that took me a solid half hour to figure out :uw_embarrassed:). The third and final thing however is that you can’t just save the secret and key files with agenix like normal but you have to strip the line endings from them 😭.</p>
<p>Here’s the basic scaffolding you’ll need for your Nix configuration:</p>
<blockquote>
<p>configuration.nix</p>
</blockquote>
<pre class="giallo z-code"><code data-lang="nix"><span class="giallo-l"><span class="z-punctuation">{</span><span class="z-variable z-parameter"> config</span><span class="z-keyword z-operator">,</span><span class="z-variable z-parameter"> pkgs</span><span class="z-keyword z-operator">, ...</span><span class="z-punctuation"> }:</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-punctuation">{</span></span>
<span class="giallo-l"><span class="z-comment">  # ... configuration options</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-entity z-other z-attribute-name z-multipart z-nix">  age</span><span class="z-source">.</span><span class="z-entity z-other z-attribute-name z-multipart z-nix">secrets</span><span class="z-keyword z-operator"> =</span><span class="z-punctuation"> {</span></span>
<span class="giallo-l"><span class="z-entity z-other z-attribute-name z-multipart z-nix">    atuin-session</span><span class="z-keyword z-operator"> =</span><span class="z-punctuation"> {</span></span>
<span class="giallo-l"><span class="z-entity z-other z-attribute-name z-multipart z-nix">        file</span><span class="z-keyword z-operator"> =</span><span class="z-string z-unquoted z-path z-nix"> ../secrets/atuin-session.age</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-entity z-other z-attribute-name z-multipart z-nix">        mode</span><span class="z-keyword z-operator"> =</span><span class="z-punctuation z-definition z-string z-string"> &quot;0444&quot;</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-punctuation">    };</span></span>
<span class="giallo-l"><span class="z-entity z-other z-attribute-name z-multipart z-nix">    atuin-key</span><span class="z-keyword z-operator"> =</span><span class="z-punctuation"> {</span></span>
<span class="giallo-l"><span class="z-entity z-other z-attribute-name z-multipart z-nix">        file</span><span class="z-keyword z-operator"> =</span><span class="z-string z-unquoted z-path z-nix"> ../secrets/atuin-key.age</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-entity z-other z-attribute-name z-multipart z-nix">        mode</span><span class="z-keyword z-operator"> =</span><span class="z-punctuation z-definition z-string z-string"> &quot;0444&quot;</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-punctuation">    };</span></span>
<span class="giallo-l"><span class="z-punctuation">  };</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-comment">  # ... more configuration options</span></span>
<span class="giallo-l"><span class="z-punctuation">}</span></span></code></pre>
<p>and then the home-manager bit</p>
<blockquote>
<p>shell.nix in home manager</p>
</blockquote>
<pre class="giallo z-code"><code data-lang="nix"><span class="giallo-l"><span class="z-punctuation">{</span><span class="z-variable z-parameter"> config</span><span class="z-keyword z-operator">,</span><span class="z-variable z-parameter"> pkgs</span><span class="z-keyword z-operator">, ...</span><span class="z-punctuation"> }:</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-punctuation">{</span></span>
<span class="giallo-l"><span class="z-comment">  # ... some home-manager modules and configs</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-entity z-other z-attribute-name z-multipart z-nix">  programs</span><span class="z-source">.</span><span class="z-entity z-other z-attribute-name z-multipart z-nix">atuin</span><span class="z-keyword z-operator"> =</span><span class="z-punctuation"> {</span></span>
<span class="giallo-l"><span class="z-entity z-other z-attribute-name z-multipart z-nix">    enable</span><span class="z-keyword z-operator"> =</span><span class="z-constant z-language"> true</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-entity z-other z-attribute-name z-multipart z-nix">    settings</span><span class="z-keyword z-operator"> =</span><span class="z-punctuation"> {</span></span>
<span class="giallo-l"><span class="z-entity z-other z-attribute-name z-multipart z-nix">      auto_sync</span><span class="z-keyword z-operator"> =</span><span class="z-constant z-language"> true</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-entity z-other z-attribute-name z-multipart z-nix">      sync_frequency</span><span class="z-keyword z-operator"> =</span><span class="z-punctuation z-definition z-string z-string"> &quot;5m&quot;</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-entity z-other z-attribute-name z-multipart z-nix">      sync_address</span><span class="z-keyword z-operator"> =</span><span class="z-punctuation z-definition z-string z-string"> &quot;https://api.atuin.sh&quot;</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-entity z-other z-attribute-name z-multipart z-nix">      search_mode</span><span class="z-keyword z-operator"> =</span><span class="z-punctuation z-definition z-string z-string"> &quot;fuzzy&quot;</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-entity z-other z-attribute-name z-multipart z-nix">      session_path</span><span class="z-keyword z-operator"> =</span><span class="z-variable z-parameter z-name z-nix"> config</span><span class="z-keyword z-operator">.</span><span class="z-variable z-parameter z-name z-nix">age</span><span class="z-keyword z-operator">.</span><span class="z-variable z-parameter z-name z-nix">secrets</span><span class="z-keyword z-operator">.</span><span class="z-punctuation z-definition z-string z-string">&quot;atuin-session&quot;</span><span class="z-keyword z-operator">.</span><span class="z-variable z-parameter z-name z-nix">path</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-entity z-other z-attribute-name z-multipart z-nix">      key_path</span><span class="z-keyword z-operator"> =</span><span class="z-variable z-parameter z-name z-nix"> config</span><span class="z-keyword z-operator">.</span><span class="z-variable z-parameter z-name z-nix">age</span><span class="z-keyword z-operator">.</span><span class="z-variable z-parameter z-name z-nix">secrets</span><span class="z-keyword z-operator">.</span><span class="z-punctuation z-definition z-string z-string">&quot;atuin-key&quot;</span><span class="z-keyword z-operator">.</span><span class="z-variable z-parameter z-name z-nix">path</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-punctuation">    };</span></span>
<span class="giallo-l"><span class="z-punctuation">  };</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-entity z-other z-attribute-name z-multipart z-nix">  age</span><span class="z-source">.</span><span class="z-entity z-other z-attribute-name z-multipart z-nix">secrets</span><span class="z-keyword z-operator"> =</span><span class="z-punctuation"> {</span></span>
<span class="giallo-l"><span class="z-entity z-other z-attribute-name z-multipart z-nix">    atuin-session</span><span class="z-keyword z-operator"> =</span><span class="z-punctuation"> {</span></span>
<span class="giallo-l"><span class="z-entity z-other z-attribute-name z-multipart z-nix">      file</span><span class="z-keyword z-operator"> =</span><span class="z-string z-unquoted z-path z-nix"> ../../secrets/atuin-session.age</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-punctuation">    };</span></span>
<span class="giallo-l"><span class="z-entity z-other z-attribute-name z-multipart z-nix">    atuin-key</span><span class="z-keyword z-operator"> =</span><span class="z-punctuation"> {</span></span>
<span class="giallo-l"><span class="z-entity z-other z-attribute-name z-multipart z-nix">      file</span><span class="z-keyword z-operator"> =</span><span class="z-string z-unquoted z-path z-nix"> ../../secrets/atuin-key.age</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-punctuation">    };</span></span>
<span class="giallo-l"><span class="z-punctuation">  };</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-comment">  # ... even more home-manager configurations 😅</span></span>
<span class="giallo-l"><span class="z-punctuation">}</span></span></code></pre>
<p>Now saving the secrets with agenix is not particularly tricky you just have to know that this is an option. Run <code>agenix -e atuin-session.age</code> and then paste in the session from <code>~/.local/share/atuin/session</code> and then instead of just saving like normal you need to run <code>:set binary</code> and then <code>:set noeol</code> and then you can save the file like normal.</p>
<p>Anyways now i’m enjoying my stats and it’s on to the next project (proly <a rel="noopener external" target="_blank" href="https://tangled.org/@dunkirk.sh/serif">serif.blue</a> 👀)</p>
<pre class="giallo z-code"><code data-lang="plain"><span class="giallo-l"><span>&gt; atuin stats</span></span>
<span class="giallo-l"><span>[▮▮▮▮▮▮▮▮▮▮] 1209 gc</span></span>
<span class="giallo-l"><span>[▮▮▮▮      ]  495 curl</span></span>
<span class="giallo-l"><span>[▮▮        ]  348 bun</span></span>
<span class="giallo-l"><span>[▮▮        ]  329 cat</span></span>
<span class="giallo-l"><span>[▮         ]  222 z</span></span>
<span class="giallo-l"><span>[▮         ]  200 g</span></span>
<span class="giallo-l"><span>[▮         ]  162 nix-shell</span></span>
<span class="giallo-l"><span>[▮         ]  145 cd</span></span>
<span class="giallo-l"><span>[▮         ]  138 vi</span></span>
<span class="giallo-l"><span>[▮         ]  138 ls</span></span>
<span class="giallo-l"><span>Total commands:   7062</span></span>
<span class="giallo-l"><span>Unique commands:  7060</span></span></code></pre>]]></content:encoded>
    </item>
    
    <item>
      <title>Adding a copy code button</title>
      <link>https://dunkirk.sh/blog/adding-a-copy-button/</link>
      <guid isPermaLink="true">https://dunkirk.sh/blog/adding-a-copy-button/</guid>
      <pubDate>Fri, 14 Mar 2025 00:00:00 +0000</pubDate>
      
      
      <dc:creator>Kieran Klukas</dc:creator>
      
      
        
      <category>accessibility</category>
        
      
      <description>
        
          &amp;lt;p&amp;gt;It took me a little over a month but I finally continued the chain of adding copy code buttons to your code blocks. It started with Salma Alam-Naylor’s &amp;lt;a rel=&amp;quot;noopener external&amp;quot; target=&amp;quot;_blank&amp;quot; href=&amp;quot;https:&#x2F;&#x2F;whitep4nth3r.com&#x2F;blog&#x2F;how-to-build-a-copy-code-snippet-button&#x2F;&amp;quot;&amp;gt;post&amp;lt;&#x2F;a&amp;gt; which I saw on Hacker News but then &amp;lt;a rel=&amp;quot;noopener external&amp;quot; target=&amp;quot;_blank&amp;quot; href=&amp;quot;https:&#x2F;&#x2F;dbushell.com&#x2F;2025&#x2F;02&#x2F;14&#x2F;copy-code-button&#x2F;&amp;quot;&amp;gt;David Bushell&amp;lt;&#x2F;a&amp;gt; also posted on it and &amp;lt;a rel=&amp;quot;noopener external&amp;quot; target=&amp;quot;_blank&amp;quot; href=&amp;quot;https:&#x2F;&#x2F;www.ragman.net&#x2F;musings&#x2F;copy_code&#x2F;&amp;quot;&amp;gt;Ragman&amp;lt;&#x2F;a&amp;gt; made a bluesky post (sky? bloop? atproto bloop? honestly not sure what a more interesting name would be) and it’s been saved in my mind since then that I should add it.&amp;lt;&#x2F;p&amp;gt;
        
      </description>
      <content:encoded><![CDATA[<p>It took me a little over a month but I finally continued the chain of adding copy code buttons to your code blocks. It started with Salma Alam-Naylor’s <a rel="noopener external" target="_blank" href="https://whitep4nth3r.com/blog/how-to-build-a-copy-code-snippet-button/">post</a> which I saw on Hacker News but then <a rel="noopener external" target="_blank" href="https://dbushell.com/2025/02/14/copy-code-button/">David Bushell</a> also posted on it and <a rel="noopener external" target="_blank" href="https://www.ragman.net/musings/copy_code/">Ragman</a> made a bluesky post (sky? bloop? atproto bloop? honestly not sure what a more interesting name would be) and it’s been saved in my mind since then that I should add it.</p>
<span id="continue-reading"></span>
<p>What finally pushed me over the edge was seeing the <a rel="noopener external" target="_blank" href="https://duckquill.daudix.one">Duckquill</a> theme and its fancy code blocks. I cloned the theme (<code>git clone https://codeberg.org/daudix/duckquill.git</code>) and figured out that the actual copy code was some reasonably simple js in <code>static/copy-button.js</code>. I copied that file and messed with it a bit as well as the css (<code>sass/_pre-container.scss</code> and some icon stuff in <code>sass/_icon.scss</code>) to make it work with my theme and style.</p>
<p>A quick hash for cache busting and import later it all worked!</p>
<blockquote>
<p>templates/head.html</p>
</blockquote>
<pre class="giallo z-code"><code data-lang="html"><span class="giallo-l"><span class="z-text">{% set jsHash = get_hash(path=&quot;js/copy-button.js&quot;, sha_type=256,</span></span>
<span class="giallo-l"><span class="z-text">base64=true) %}</span></span>
<span class="giallo-l"><span class="z-punctuation z-definition z-tag">&lt;</span><span class="z-entity z-name z-tag">script</span></span>
<span class="giallo-l"><span class="z-entity z-other z-attribute-name">  src</span><span class="z-punctuation z-separator z-key-value">=</span><span class="z-punctuation z-definition z-string z-string">&quot;{{ get_url(path=&#39;js/copy-button.js?&#39; ~ jsHash, trailing_slash=false) | safe }}&quot;</span></span>
<span class="giallo-l"><span class="z-entity z-other z-attribute-name">  defer</span></span>
<span class="giallo-l"><span class="z-punctuation z-definition z-tag">&gt;</span><span class="z-source">&lt;</span><span class="z-punctuation z-definition z-tag">/</span><span class="z-entity z-name z-tag">script</span><span class="z-punctuation z-definition z-tag">&gt;</span></span></code></pre>
<p>The one thing I expanded on was the ability to specify a file name / comment for the code block. When js is disabled a markdown <code>&gt;</code> blockquote on the line before the code block will create a header tab for the code block. I snipped the header tab idea from <a rel="noopener external" target="_blank" href="https://chevyray.dev">chevyray.dev</a> and I grew to quite like it so I didn’t want to abandon it over a copy button.</p>
<p>Here is my code should you want to use it:</p>
<blockquote>
<p>static/js/copy-button.js</p>
</blockquote>
<pre class="giallo z-code"><code data-lang="javascript"><span class="giallo-l"><span class="z-punctuation z-definition z-comment z-comment">// Based on https://www.roboleary.net/2022/01/13/copy-code-to-clipboard-blog.html</span></span>
<span class="giallo-l"><span class="z-source">document</span><span class="z-punctuation z-accessor">.</span><span class="z-entity z-name z-function">addEventListener</span><span class="z-source">(</span><span class="z-punctuation z-definition z-string z-string">&quot;DOMContentLoaded&quot;</span><span class="z-punctuation">, ()</span><span class="z-storage z-type"> =&gt;</span><span class="z-punctuation"> {</span></span>
<span class="giallo-l"><span class="z-storage z-type">  const</span><span class="z-variable z-other z-constant z-js"> blocks</span><span class="z-keyword z-operator"> =</span><span class="z-source"> document</span><span class="z-punctuation z-accessor">.</span><span class="z-entity z-name z-function">querySelectorAll</span><span class="z-source">(</span><span class="z-punctuation z-definition z-string z-string">&quot;pre[class^=&#39;language-&#39;]&quot;</span><span class="z-source">)</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-keyword">  for</span><span class="z-source"> (</span><span class="z-storage z-type">const</span><span class="z-variable z-other z-constant z-js"> block</span><span class="z-keyword z-operator z-expression z-of z-js"> of</span><span class="z-variable z-other z-readwrite z-source"> blocks)</span><span class="z-punctuation"> {</span></span>
<span class="giallo-l"><span class="z-keyword">    if</span><span class="z-source"> (navigator</span><span class="z-punctuation z-accessor">.</span><span class="z-variable z-other z-property z-js z-source">clipboard)</span><span class="z-punctuation"> {</span></span>
<span class="giallo-l"><span class="z-punctuation z-definition z-comment z-comment">      // Code block header title</span></span>
<span class="giallo-l"><span class="z-storage z-type">      const</span><span class="z-variable z-other z-constant z-js"> title</span><span class="z-keyword z-operator"> =</span><span class="z-source"> document</span><span class="z-punctuation z-accessor">.</span><span class="z-entity z-name z-function">createElement</span><span class="z-source">(</span><span class="z-punctuation z-definition z-string z-string">&quot;span&quot;</span><span class="z-source">)</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-storage z-type">      const</span><span class="z-variable z-other z-constant z-js"> lang</span><span class="z-keyword z-operator"> =</span><span class="z-source"> block</span><span class="z-punctuation z-accessor">.</span><span class="z-entity z-name z-function">getAttribute</span><span class="z-source">(</span><span class="z-punctuation z-definition z-string z-string">&quot;data-lang&quot;</span><span class="z-source">)</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-storage z-type">      const</span><span class="z-variable z-other z-constant z-js"> comment</span><span class="z-keyword z-operator"> =</span></span>
<span class="giallo-l"><span class="z-source">        block</span><span class="z-punctuation z-accessor">.</span><span class="z-variable z-other z-property z-js">previousElementSibling</span><span class="z-keyword z-operator"> &amp;&amp;</span></span>
<span class="giallo-l"><span class="z-source">        (block</span><span class="z-punctuation z-accessor">.</span><span class="z-source">previousElementSibling</span><span class="z-punctuation z-accessor">.</span><span class="z-variable z-other z-property z-js">tagName</span><span class="z-keyword z-operator"> ===</span><span class="z-punctuation z-definition z-string z-string"> &quot;blockquote&quot;</span><span class="z-keyword z-operator"> ||</span></span>
<span class="giallo-l"><span class="z-source">          block</span><span class="z-punctuation z-accessor">.</span><span class="z-source">previousElementSibling</span><span class="z-punctuation z-accessor">.</span><span class="z-variable z-other z-property z-js">nodeName</span><span class="z-keyword z-operator"> ===</span><span class="z-punctuation z-definition z-string z-string"> &quot;BLOCKQUOTE&quot;</span><span class="z-source">)</span></span>
<span class="giallo-l"><span class="z-keyword z-operator">          ?</span><span class="z-source"> block</span><span class="z-punctuation z-accessor">.</span><span class="z-variable z-other z-property z-js">previousElementSibling</span></span>
<span class="giallo-l"><span class="z-keyword z-operator">          :</span><span class="z-constant z-language z-null z-js"> null</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-keyword">      if</span><span class="z-source z-variable z-other z-readwrite"> (comment) block</span><span class="z-punctuation z-accessor">.</span><span class="z-source">previousElementSibling</span><span class="z-punctuation z-accessor">.</span><span class="z-entity z-name z-function">remove</span><span class="z-source">()</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-source">      title</span><span class="z-punctuation z-accessor">.</span><span class="z-variable z-other z-property z-js">innerHTML</span><span class="z-keyword z-operator"> =</span></span>
<span class="giallo-l"><span class="z-variable z-other z-readwrite">        lang</span><span class="z-keyword z-operator"> +</span><span class="z-source z-variable z-other z-readwrite"> (comment</span><span class="z-keyword z-operator"> ?</span><span class="z-punctuation z-definition z-string z-string"> ` (</span><span class="z-punctuation">${</span><span class="z-variable">comment</span><span class="z-punctuation z-accessor">.</span><span class="z-variable">textContent</span><span class="z-punctuation z-accessor">.</span><span class="z-entity z-name z-function">trim</span><span class="z-string">()</span><span class="z-punctuation">}</span><span class="z-string z-punctuation z-definition z-string">)`</span><span class="z-keyword z-operator"> :</span><span class="z-punctuation z-definition z-string"> &quot;&quot;</span><span class="z-source">)</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-punctuation z-definition z-comment z-comment">      // Copy button icon</span></span>
<span class="giallo-l"><span class="z-storage z-type">      const</span><span class="z-variable z-other z-constant z-js"> icon</span><span class="z-keyword z-operator"> =</span><span class="z-source"> document</span><span class="z-punctuation z-accessor">.</span><span class="z-entity z-name z-function">createElement</span><span class="z-source">(</span><span class="z-punctuation z-definition z-string z-string">&quot;i&quot;</span><span class="z-source">)</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-source">      icon</span><span class="z-punctuation z-accessor">.</span><span class="z-source">classList</span><span class="z-punctuation z-accessor">.</span><span class="z-entity z-name z-function">add</span><span class="z-source">(</span><span class="z-punctuation z-definition z-string z-string">&quot;icon&quot;</span><span class="z-source">)</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-punctuation z-definition z-comment z-comment">      // Copy button</span></span>
<span class="giallo-l"><span class="z-storage z-type">      const</span><span class="z-variable z-other z-constant z-js"> button</span><span class="z-keyword z-operator"> =</span><span class="z-source"> document</span><span class="z-punctuation z-accessor">.</span><span class="z-entity z-name z-function">createElement</span><span class="z-source">(</span><span class="z-punctuation z-definition z-string z-string">&quot;button&quot;</span><span class="z-source">)</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-storage z-type">      const</span><span class="z-variable z-other z-constant z-js"> copyCodeText</span><span class="z-keyword z-operator"> =</span><span class="z-punctuation z-definition z-string z-string"> &quot;Copy code&quot;</span><span class="z-punctuation">;</span><span class="z-punctuation z-definition z-comment z-comment"> // Use hardcoded text instead of getElementById</span></span>
<span class="giallo-l"><span class="z-source">      button</span><span class="z-punctuation z-accessor">.</span><span class="z-entity z-name z-function">setAttribute</span><span class="z-source">(</span><span class="z-punctuation z-definition z-string z-string">&quot;title&quot;</span><span class="z-punctuation">,</span><span class="z-variable z-other z-readwrite z-source"> copyCodeText)</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-source">      button</span><span class="z-punctuation z-accessor">.</span><span class="z-entity z-name z-function">appendChild</span><span class="z-source z-variable z-other z-readwrite">(icon)</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-punctuation z-definition z-comment z-comment">      // Code block header</span></span>
<span class="giallo-l"><span class="z-storage z-type">      const</span><span class="z-variable z-other z-constant z-js"> header</span><span class="z-keyword z-operator"> =</span><span class="z-source"> document</span><span class="z-punctuation z-accessor">.</span><span class="z-entity z-name z-function">createElement</span><span class="z-source">(</span><span class="z-punctuation z-definition z-string z-string">&quot;div&quot;</span><span class="z-source">)</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-source">      header</span><span class="z-punctuation z-accessor">.</span><span class="z-source">classList</span><span class="z-punctuation z-accessor">.</span><span class="z-entity z-name z-function">add</span><span class="z-source">(</span><span class="z-punctuation z-definition z-string z-string">&quot;header&quot;</span><span class="z-source">)</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-source">      header</span><span class="z-punctuation z-accessor">.</span><span class="z-entity z-name z-function">appendChild</span><span class="z-source z-variable z-other z-readwrite">(title)</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-source">      header</span><span class="z-punctuation z-accessor">.</span><span class="z-entity z-name z-function">appendChild</span><span class="z-source z-variable z-other z-readwrite">(button)</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-punctuation z-definition z-comment z-comment">      // Container that holds header and the code block itself</span></span>
<span class="giallo-l"><span class="z-storage z-type">      const</span><span class="z-variable z-other z-constant z-js"> container</span><span class="z-keyword z-operator"> =</span><span class="z-source"> document</span><span class="z-punctuation z-accessor">.</span><span class="z-entity z-name z-function">createElement</span><span class="z-source">(</span><span class="z-punctuation z-definition z-string z-string">&quot;div&quot;</span><span class="z-source">)</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-source">      container</span><span class="z-punctuation z-accessor">.</span><span class="z-source">classList</span><span class="z-punctuation z-accessor">.</span><span class="z-entity z-name z-function">add</span><span class="z-source">(</span><span class="z-punctuation z-definition z-string z-string">&quot;pre-container&quot;</span><span class="z-source">)</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-source">      container</span><span class="z-punctuation z-accessor">.</span><span class="z-entity z-name z-function">appendChild</span><span class="z-source z-variable z-other z-readwrite">(header)</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-punctuation z-definition z-comment z-comment">      // Move code block into the container</span></span>
<span class="giallo-l"><span class="z-source">      block</span><span class="z-punctuation z-accessor">.</span><span class="z-source">parentNode</span><span class="z-punctuation z-accessor">.</span><span class="z-entity z-name z-function">insertBefore</span><span class="z-source z-variable z-other z-readwrite">(container</span><span class="z-punctuation">,</span><span class="z-variable z-other z-readwrite z-source"> block)</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-source">      container</span><span class="z-punctuation z-accessor">.</span><span class="z-entity z-name z-function">appendChild</span><span class="z-source z-variable z-other z-readwrite">(block)</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-source">      button</span><span class="z-punctuation z-accessor">.</span><span class="z-entity z-name z-function">addEventListener</span><span class="z-source">(</span><span class="z-punctuation z-definition z-string z-string">&quot;click&quot;</span><span class="z-punctuation">,</span><span class="z-storage z-modifier"> async</span><span class="z-punctuation"> ()</span><span class="z-storage z-type"> =&gt;</span><span class="z-punctuation"> {</span></span>
<span class="giallo-l"><span class="z-keyword">        await</span><span class="z-entity z-name z-function"> copyCode</span><span class="z-source z-variable z-other z-readwrite">(block</span><span class="z-punctuation">,</span><span class="z-variable z-other z-readwrite"> header</span><span class="z-punctuation">,</span><span class="z-variable z-other z-readwrite z-source"> button)</span><span class="z-punctuation">;</span><span class="z-punctuation z-definition z-comment z-comment"> // Pass the button here</span></span>
<span class="giallo-l"><span class="z-punctuation">      }</span><span class="z-source">)</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-punctuation">    }</span></span>
<span class="giallo-l"><span class="z-punctuation">  }</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-storage z-modifier z-storage z-type">  async function</span><span class="z-entity z-name z-function"> copyCode</span><span class="z-punctuation">(</span><span class="z-variable z-parameter">block</span><span class="z-punctuation">,</span><span class="z-variable z-parameter"> header</span><span class="z-punctuation">,</span><span class="z-variable z-parameter"> button</span><span class="z-punctuation">) {</span></span>
<span class="giallo-l"><span class="z-storage z-type">    const</span><span class="z-variable z-other z-constant z-js"> code</span><span class="z-keyword z-operator"> =</span><span class="z-source"> block</span><span class="z-punctuation z-accessor">.</span><span class="z-entity z-name z-function">querySelector</span><span class="z-source">(</span><span class="z-punctuation z-definition z-string z-string">&quot;code&quot;</span><span class="z-source">)</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-storage z-type">    const</span><span class="z-variable z-other z-constant z-js"> text</span><span class="z-keyword z-operator"> =</span><span class="z-source"> code</span><span class="z-punctuation z-accessor">.</span><span class="z-variable z-other z-property z-js">innerText</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-keyword">    await</span><span class="z-source"> navigator</span><span class="z-punctuation z-accessor">.</span><span class="z-source">clipboard</span><span class="z-punctuation z-accessor">.</span><span class="z-entity z-name z-function">writeText</span><span class="z-source z-variable z-other z-readwrite">(text)</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-source">    header</span><span class="z-punctuation z-accessor">.</span><span class="z-source">classList</span><span class="z-punctuation z-accessor">.</span><span class="z-entity z-name z-function">add</span><span class="z-source">(</span><span class="z-punctuation z-definition z-string z-string">&quot;active&quot;</span><span class="z-source">)</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-source">    button</span><span class="z-punctuation z-accessor">.</span><span class="z-entity z-name z-function">setAttribute</span><span class="z-source">(</span><span class="z-punctuation z-definition z-string z-string">&quot;disabled&quot;</span><span class="z-punctuation">,</span><span class="z-constant z-language z-boolean"> true</span><span class="z-source">)</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-source">    header</span><span class="z-punctuation z-accessor">.</span><span class="z-entity z-name z-function">addEventListener</span><span class="z-source">(</span></span>
<span class="giallo-l"><span class="z-punctuation z-definition z-string z-string">      &quot;animationend&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">      ()</span><span class="z-storage z-type"> =&gt;</span><span class="z-punctuation"> {</span></span>
<span class="giallo-l"><span class="z-source">        header</span><span class="z-punctuation z-accessor">.</span><span class="z-source">classList</span><span class="z-punctuation z-accessor">.</span><span class="z-entity z-name z-function">remove</span><span class="z-source">(</span><span class="z-punctuation z-definition z-string z-string">&quot;active&quot;</span><span class="z-source">)</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-source">        button</span><span class="z-punctuation z-accessor">.</span><span class="z-entity z-name z-function">removeAttribute</span><span class="z-source">(</span><span class="z-punctuation z-definition z-string z-string">&quot;disabled&quot;</span><span class="z-source">)</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-punctuation">      },</span></span>
<span class="giallo-l"><span class="z-punctuation">      {</span><span class="z-source"> once</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-constant z-language z-boolean"> true</span><span class="z-punctuation"> },</span></span>
<span class="giallo-l"><span class="z-source">    )</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-punctuation">  }</span></span>
<span class="giallo-l"><span class="z-punctuation">}</span><span class="z-source">)</span><span class="z-punctuation">;</span></span></code></pre>
<p>and the css:</p>
<blockquote>
<p>sass/css/_copy-button.scss</p>
</blockquote>
<pre class="giallo z-code"><code data-lang="scss"><span class="giallo-l"><span class="z-entity z-name z-tag">i</span><span class="z-punctuation">.</span><span class="z-entity z-other z-attribute-name z-class z-css">icon</span><span class="z-punctuation"> {</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">    display</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source"> inline-block</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">    mask-size</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source"> cover</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">    background-color</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source"> currentColor</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">    width</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-constant z-numeric"> 1</span><span class="z-keyword">rem</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">    height</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-constant z-numeric"> 1</span><span class="z-keyword">rem</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">    font-style</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source"> normal</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">    font-variant</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source"> normal</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">    line-height</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-constant z-numeric"> 0</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">    text-rendering</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source"> auto</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-punctuation">}</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-punctuation">.</span><span class="z-entity z-other z-attribute-name z-class z-css">pre-container</span><span class="z-punctuation"> {</span></span>
<span class="giallo-l"><span class="z-variable">    --icon-copy</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-support z-function z-misc"> url</span><span class="z-punctuation">(</span><span class="z-punctuation z-definition z-string z-string">&quot;data:image/svg+xml,%3Csvg viewBox=&#39;0 0 16 16&#39; height=&#39;16&#39; width=&#39;16&#39; xmlns=&#39;http://www.w3.org/2000/svg&#39;%3E%3Cpath d=&#39;M0 3c0-1.645 1.355-3 3-3h5c1.645 0 3 1.355 3 3 0 .55-.45 1-1 1s-1-.45-1-1c0-.57-.43-1-1-1H3c-.57 0-1 .43-1 1v5c0 .57.43 1 1 1 .55 0 1 .45 1 1s-.45 1-1 1c-1.645 0-3-1.355-3-3zm5 5c0-1.645 1.355-3 3-3h5c1.645 0 3 1.355 3 3v5c0 1.645-1.355 3-3 3H8c-1.645 0-3-1.355-3-3zm2 0v5c0 .57.43 1 1 1h5c.57 0 1-.43 1-1V8c0-.57-.43-1-1-1H8c-.57 0-1 .43-1 1m0 0&#39;/%3E%3C/svg%3E&quot;</span><span class="z-punctuation">);</span></span>
<span class="giallo-l"><span class="z-variable">    --icon-done</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-support z-function z-misc"> url</span><span class="z-punctuation">(</span><span class="z-punctuation z-definition z-string z-string">&quot;data:image/svg+xml,%3Csvg xmlns=&#39;http://www.w3.org/2000/svg&#39; width=&#39;16&#39; height=&#39;16&#39;%3E%3Cpath d=&#39;M7.883 0q-.486.008-.965.074a7.98 7.98 0 0 0-4.602 2.293 8.01 8.01 0 0 0-1.23 9.664 8.015 8.015 0 0 0 9.02 3.684 8 8 0 0 0 5.89-7.75 1 1 0 1 0-2 .008 5.986 5.986 0 0 1-4.418 5.816 5.996 5.996 0 0 1-6.762-2.766 5.99 5.99 0 0 1 .922-7.25 5.99 5.99 0 0 1 7.239-.984 1 1 0 0 0 1.363-.371c.273-.48.11-1.09-.371-1.367A8 8 0 0 0 9.492.14 8 8 0 0 0 7.882 0m7.15 1.998-.1.002a1 1 0 0 0-.687.34L7.95 9.535 5.707 7.29A1 1 0 0 0 4 8a1 1 0 0 0 .293.707l3 3c.195.195.465.3.742.293.277-.012.535-.133.719-.344l7-8A1 1 0 0 0 16 2.934a1 1 0 0 0-.34-.688 1 1 0 0 0-.627-.248&#39;/%3E%3C/svg%3E&quot;</span><span class="z-punctuation">);</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">    margin</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-constant z-numeric"> 1</span><span class="z-keyword">rem</span><span class="z-constant z-numeric"> 0 1</span><span class="z-keyword">rem</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">    border-radius</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-constant z-numeric"> 0.75</span><span class="z-keyword">rem</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-punctuation">    .</span><span class="z-entity z-other z-attribute-name z-class z-css">header</span><span class="z-punctuation"> {</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">        display</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source"> flex</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">        justify-content</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source"> space-between</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">        align-items</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source"> center</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">        border-radius</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-constant z-numeric"> 0.2</span><span class="z-keyword">em</span><span class="z-constant z-numeric"> 0.2</span><span class="z-keyword">em</span><span class="z-constant z-numeric"> 0 0</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">        background-color</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-support z-function z-misc"> var</span><span class="z-punctuation">(</span><span class="z-variable">--accent</span><span class="z-punctuation">);</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">        background-size</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-constant z-numeric z-keyword z-other z-unit z-percentage z-css"> 200%</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">        padding</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-constant z-numeric"> 0.25</span><span class="z-keyword">rem</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">        height</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-constant z-numeric"> 2.5</span><span class="z-keyword">rem</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-entity z-name z-tag">        span</span><span class="z-punctuation"> {</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">            margin-inline-start</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-constant z-numeric"> 0.75</span><span class="z-keyword">rem</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">            color</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-support z-function z-misc"> var</span><span class="z-punctuation">(</span><span class="z-variable">--purple-gray</span><span class="z-punctuation">);</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">            font-weight</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source"> bold</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">            line-height</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-constant z-numeric"> 1</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-punctuation">        }</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-entity z-name z-tag">        button</span><span class="z-punctuation"> {</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">            appearance</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source"> none</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">            transition</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-constant z-numeric"> 200</span><span class="z-keyword">ms</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag">            cursor</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source"> pointer</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">            border</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source"> none</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">            border-radius</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-constant z-numeric"> 0.4</span><span class="z-keyword">rem</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">            background-color</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source"> transparent</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">            padding</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-constant z-numeric"> 0.5</span><span class="z-keyword">rem</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">            color</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-support z-function z-misc"> var</span><span class="z-punctuation">(</span><span class="z-variable">--purple-gray</span><span class="z-punctuation">);</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">            line-height</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-constant z-numeric"> 0</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-entity z-name z-tag">            &amp;</span><span class="z-punctuation">:</span><span class="z-entity z-other z-attribute-name z-pseudo-class">hover</span><span class="z-punctuation"> {</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">                background-color</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-support z-function z-misc"> color-mix</span><span class="z-punctuation">(</span></span>
<span class="giallo-l"><span class="z-variable z-parameter z-url">                    in oklab</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-support z-function z-misc">                    var</span><span class="z-punctuation">(</span><span class="z-variable">--accent</span><span class="z-punctuation">)</span><span class="z-constant z-numeric z-keyword z-other z-unit z-percentage z-css"> 80%</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-support z-function z-misc">                    var</span><span class="z-punctuation">(</span><span class="z-variable">--purple-gray</span><span class="z-punctuation">)</span></span>
<span class="giallo-l"><span class="z-punctuation">                );</span></span>
<span class="giallo-l"><span class="z-punctuation">            }</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-entity z-name z-tag">            &amp;</span><span class="z-punctuation">:</span><span class="z-entity z-other z-attribute-name z-pseudo-class">focus</span><span class="z-punctuation"> {</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">                background-color</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-support z-function z-misc"> color-mix</span><span class="z-punctuation">(</span></span>
<span class="giallo-l"><span class="z-variable z-parameter z-url">                    in oklab</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-support z-function z-misc">                    var</span><span class="z-punctuation">(</span><span class="z-variable">--accent</span><span class="z-punctuation">)</span><span class="z-constant z-numeric z-keyword z-other z-unit z-percentage z-css"> 80%</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-support z-function z-misc">                    var</span><span class="z-punctuation">(</span><span class="z-variable">--purple-gray</span><span class="z-punctuation">)</span></span>
<span class="giallo-l"><span class="z-punctuation">                );</span></span>
<span class="giallo-l"><span class="z-punctuation">            }</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-entity z-name z-tag">            &amp;</span><span class="z-punctuation">:</span><span class="z-entity z-other z-attribute-name z-pseudo-class">active</span><span class="z-punctuation"> {</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">                transform</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-support z-function z-misc"> scale</span><span class="z-punctuation">(</span><span class="z-constant z-numeric">0.9</span><span class="z-punctuation">);</span></span>
<span class="giallo-l"><span class="z-punctuation">            }</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-entity z-name z-tag">            &amp;</span><span class="z-punctuation">:</span><span class="z-entity z-other z-attribute-name z-pseudo-class">disabled</span><span class="z-punctuation"> {</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag">                cursor</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source"> not-allowed</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-entity z-name z-tag">                &amp;</span><span class="z-punctuation">:</span><span class="z-entity z-other z-attribute-name z-pseudo-class">active</span><span class="z-punctuation"> {</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">                    transform</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source"> none</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-punctuation">                }</span></span>
<span class="giallo-l"><span class="z-punctuation">            }</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-punctuation">            .</span><span class="z-entity z-other z-attribute-name z-class z-css">icon</span><span class="z-punctuation"> {</span></span>
<span class="giallo-l"><span class="z-support z-type z-vendored z-property-name">                -webkit-mask-image</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-support z-function z-misc"> var</span><span class="z-punctuation">(</span><span class="z-variable">--icon-copy</span><span class="z-punctuation">);</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">                mask-image</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-support z-function z-misc"> var</span><span class="z-punctuation">(</span><span class="z-variable">--icon-copy</span><span class="z-punctuation">);</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">                transition</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-constant z-numeric"> 200</span><span class="z-keyword">ms</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-punctuation">                :</span><span class="z-entity z-other z-attribute-name z-pseudo-class">root</span><span class="z-punctuation">[</span><span class="z-entity z-other z-attribute-name">dir</span><span class="z-keyword z-operator">*=</span><span class="z-punctuation z-definition z-string z-meta z-attribute-selector">&quot;rtl&quot;</span><span class="z-punctuation">]</span><span class="z-entity z-name z-tag"> &amp;</span><span class="z-punctuation"> {</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">                    transform</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-support z-function z-misc"> scaleX</span><span class="z-punctuation">(</span><span class="z-constant z-numeric">-1</span><span class="z-punctuation">);</span></span>
<span class="giallo-l"><span class="z-punctuation">                }</span></span>
<span class="giallo-l"><span class="z-punctuation">            }</span></span>
<span class="giallo-l"><span class="z-punctuation">        }</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-entity z-name z-tag">        &amp;</span><span class="z-punctuation">.</span><span class="z-entity z-other z-attribute-name z-class z-css">active</span><span class="z-punctuation"> {</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag">            button</span><span class="z-punctuation"> {</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">                animation</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source"> active-copy </span><span class="z-constant z-numeric">0.3</span><span class="z-keyword">s</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">                color</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-support z-function z-misc"> var</span><span class="z-punctuation">(</span><span class="z-variable">--purple-gray</span><span class="z-punctuation">);</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-punctuation">                .</span><span class="z-entity z-other z-attribute-name z-class z-css">icon</span><span class="z-punctuation"> {</span></span>
<span class="giallo-l"><span class="z-support z-type z-vendored z-property-name">                    -webkit-mask-image</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-support z-function z-misc"> var</span><span class="z-punctuation">(</span><span class="z-variable">--icon-done</span><span class="z-punctuation">);</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">                    mask-image</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-support z-function z-misc"> var</span><span class="z-punctuation">(</span><span class="z-variable">--icon-done</span><span class="z-punctuation">);</span></span>
<span class="giallo-l"><span class="z-punctuation">                }</span></span>
<span class="giallo-l"><span class="z-punctuation">            }</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-punctuation z-definition z-keyword z-keyword">            @keyframes</span><span class="z-entity z-name z-function"> active-copy</span><span class="z-punctuation"> {</span></span>
<span class="giallo-l"><span class="z-entity z-other z-attribute-name">                50%</span><span class="z-punctuation"> {</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">                    transform</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-support z-function z-misc"> scale</span><span class="z-punctuation">(</span><span class="z-constant z-numeric">0.9</span><span class="z-punctuation">);</span></span>
<span class="giallo-l"><span class="z-punctuation">                }</span></span>
<span class="giallo-l"><span class="z-entity z-other z-attribute-name">                100%</span><span class="z-punctuation"> {</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">                    transform</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source"> none</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-punctuation">                }</span></span>
<span class="giallo-l"><span class="z-punctuation">            }</span></span>
<span class="giallo-l"><span class="z-punctuation">        }</span></span>
<span class="giallo-l"><span class="z-punctuation">    }</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-entity z-name z-tag">    pre</span><span class="z-punctuation"> {</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">        margin</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-constant z-numeric"> 0</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">        box-shadow</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source"> none</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">        border-radius</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-constant z-numeric"> 0 0 0.2</span><span class="z-keyword">em</span><span class="z-constant z-numeric"> 0.2</span><span class="z-keyword">em</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-punctuation">    }</span></span>
<span class="giallo-l"><span class="z-punctuation">}</span></span></code></pre>]]></content:encoded>
    </item>
    
    <item>
      <title>All my animation projects</title>
      <link>https://dunkirk.sh/blog/my-animations/</link>
      <guid isPermaLink="true">https://dunkirk.sh/blog/my-animations/</guid>
      <pubDate>Fri, 14 Mar 2025 00:00:00 +0000</pubDate>
      
      
      <dc:creator>Kieran Klukas</dc:creator>
      
      
        
      <category>tool</category>
        
      <category>fancy</category>
        
      <category>physics</category>
        
      
      <description>
        
          &amp;lt;p&amp;gt;The other day I realized that I never made a page to collect all of my animation projects in one place. I’ve made quite a few of them over the years and untill now they have just been sitting in a giant folder on my nas. Now they are all in a nice clean collection here 🎉&amp;lt;&#x2F;p&amp;gt;
        
      </description>
      <content:encoded><![CDATA[<p>The other day I realized that I never made a page to collect all of my animation projects in one place. I’ve made quite a few of them over the years and untill now they have just been sitting in a giant folder on my nas. Now they are all in a nice clean collection here 🎉</p>
<span id="continue-reading"></span><h2 id="2021">2021</h2>
<figure class="yt-embed">
    <iframe
        src="https://www.youtube-nocookie.com/embed/O7SYcdUM8mI"
        allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
        webkitallowfullscreen
        mozallowfullscreen
        allowfullscreen
    >
    </iframe>
    
    <figcaption>2021.01.27 first jelly jar</figcaption>
    
</figure>
<figure class="02 10" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;zfES0NVx7vcr.webp" alt="tesla in a showroom with fire jets"  />
    </div>
    
    <figcaption><p>2021.02.10 tesla showroom</p>
</figcaption>
    
</figure>
<figure class="yt-embed">
    <iframe
        src="https://www.youtube-nocookie.com/embed/7Ozt7WcVwt0"
        allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
        webkitallowfullscreen
        mozallowfullscreen
        allowfullscreen
    >
    </iframe>
    
    <figcaption>2021.09.27 Chalet a la Tagia minecraft animation</figcaption>
    
</figure>
<figure class="12 15" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;qa26wPWOhZUn.webp" alt="cube diorama"  />
    </div>
    
    <figcaption><p>2021.12.15 cube diorama</p>
</figcaption>
    
</figure>
<figure class="yt-embed">
    <iframe
        src="https://www.youtube-nocookie.com/embed/O5iHoFwKQuE"
        allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
        webkitallowfullscreen
        mozallowfullscreen
        allowfullscreen
    >
    </iframe>
    
    <figcaption>2021.12.17 creature walk cycle test</figcaption>
    
</figure>
<figure class="yt-embed">
    <iframe
        src="https://www.youtube-nocookie.com/embed/Mh4BL8O6-i8"
        allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
        webkitallowfullscreen
        mozallowfullscreen
        allowfullscreen
    >
    </iframe>
    
    <figcaption>2021.12.21 minecraft water vfx test</figcaption>
    
</figure>
<figure class="yt-embed">
    <iframe
        src="https://www.youtube-nocookie.com/embed/xBn43UU_jak"
        allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
        webkitallowfullscreen
        mozallowfullscreen
        allowfullscreen
    >
    </iframe>
    
    <figcaption>2021.12.23 fireball smoke &#x2F; fire sim</figcaption>
    
</figure>
<h2 id="2022">2022</h2>
<figure class="yt-embed">
    <iframe
        src="https://www.youtube-nocookie.com/embed/R9SwANdkMf0"
        allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
        webkitallowfullscreen
        mozallowfullscreen
        allowfullscreen
    >
    </iframe>
    
    <figcaption>2022.01.05 wave ball motion effects</figcaption>
    
</figure>
<figure class="yt-embed">
    <iframe
        src="https://www.youtube-nocookie.com/embed/ru5QfeVqlUY"
        allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
        webkitallowfullscreen
        mozallowfullscreen
        allowfullscreen
    >
    </iframe>
    
    <figcaption>2022.01.12 sunroom plant vfx</figcaption>
    
</figure>
<figure class="yt-embed">
    <iframe
        src="https://www.youtube-nocookie.com/embed/bHqE4aHSMLU"
        allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
        webkitallowfullscreen
        mozallowfullscreen
        allowfullscreen
    >
    </iframe>
    
    <figcaption>2022.01.20 star chase</figcaption>
    
</figure>
<figure class="yt-embed">
    <iframe
        src="https://www.youtube-nocookie.com/embed/3JwTkVJ2WxU"
        allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
        webkitallowfullscreen
        mozallowfullscreen
        allowfullscreen
    >
    </iframe>
    
    <figcaption>2022.01.21 star chase v2 in tunnel</figcaption>
    
</figure>
<figure class="yt-embed">
    <iframe
        src="https://www.youtube-nocookie.com/embed/mNeEJ-VE0o8"
        allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
        webkitallowfullscreen
        mozallowfullscreen
        allowfullscreen
    >
    </iframe>
    
    <figcaption>2022.01.21 particle path guides test</figcaption>
    
</figure>
<figure class="yt-embed">
    <iframe
        src="https://www.youtube-nocookie.com/embed/Gy0K-Gi95Jg"
        allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
        webkitallowfullscreen
        mozallowfullscreen
        allowfullscreen
    >
    </iframe>
    
    <figcaption>2022.01.22 lost music visualization</figcaption>
    
</figure>
<figure class="01 24" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;qrRxhAI_U04m.webp" alt="ice sphere"  />
    </div>
    
    <figcaption><p>2022.01.24 ice icosphere</p>
</figcaption>
    
</figure>
<figure class="01 27" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;JQnWbotNPjCV.webp" alt="glass jar with marbles"  />
    </div>
    
    <figcaption><p>2022.01.27 marble jar</p>
</figcaption>
    
</figure>
<figure class="yt-embed">
    <iframe
        src="https://www.youtube-nocookie.com/embed/ue-hy7w1-JE"
        allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
        webkitallowfullscreen
        mozallowfullscreen
        allowfullscreen
    >
    </iframe>
    
    <figcaption>2022.02.08 firefly particle sim</figcaption>
    
</figure>
<figure class="yt-embed">
    <iframe
        src="https://www.youtube-nocookie.com/embed/JrzjPBDBPF0"
        allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
        webkitallowfullscreen
        mozallowfullscreen
        allowfullscreen
    >
    </iframe>
    
    <figcaption>2022.02.09 color changing blocks vfx</figcaption>
    
</figure>
<figure class="yt-embed">
    <iframe
        src="https://www.youtube-nocookie.com/embed/fh_cNR9QhdU"
        allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
        webkitallowfullscreen
        mozallowfullscreen
        allowfullscreen
    >
    </iframe>
    
    <figcaption>2022.02.25 attempt at an epic chase scene</figcaption>
    
</figure>
<figure class="yt-embed">
    <iframe
        src="https://www.youtube-nocookie.com/embed/BGJbmXqCD5M"
        allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
        webkitallowfullscreen
        mozallowfullscreen
        allowfullscreen
    >
    </iframe>
    
    <figcaption>2022.03.16 molecular plexus</figcaption>
    
</figure>
<figure class="03 16" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;Lh-BLtahNCD3.webp" alt="twisted torus with flattened sphere in the center"  />
    </div>
    
    <figcaption><p>2022.03.16 twisted torus</p>
</figcaption>
    
</figure>
<figure class="yt-embed">
    <iframe
        src="https://www.youtube-nocookie.com/embed/yT37oZmd4hc"
        allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
        webkitallowfullscreen
        mozallowfullscreen
        allowfullscreen
    >
    </iframe>
    
    <figcaption>2022.03.17 hex tunnel</figcaption>
    
</figure>
<figure class="yt-embed">
    <iframe
        src="https://www.youtube-nocookie.com/embed/3SQN0L0wbhU"
        allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
        webkitallowfullscreen
        mozallowfullscreen
        allowfullscreen
    >
    </iframe>
    
    <figcaption>2022.03.23 wavy strips motion effects</figcaption>
    
</figure>
<figure class="03 31" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;n4kJfXQbHpxp.webp" alt="airship far bottom"  />
    </div>
    
    <figcaption><p>2022.03.31 airship</p>
</figcaption>
    
</figure>
<figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;MazhxSUOshdY.webp" alt="airship far side"  />
    </div>
    
</figure>
<figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;Qty_GYWgxJi6.webp" alt="airship front top"  />
    </div>
    
</figure>
<figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;y8ROtoPXYWYP.webp" alt="airship front cab"  />
    </div>
    
</figure>
<figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;Rr9jqcY8EPqn.webp" alt="airship front side"  />
    </div>
    
</figure>
<figure class="04 06" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;KOOEOTt_6dDs.webp" alt="minecraft village front door with villager"  />
    </div>
    
    <figcaption><p>2022.04.06 viking village</p>
</figcaption>
    
</figure>
<figure class="04 06" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;DKf4tgKVpq6b.webp" alt="minecraft village from across a small lake with a skeleton and witch"  />
    </div>
    
    <figcaption><p>2022.04.06 mountain lake village</p>
</figcaption>
    
</figure>
<figure class="yt-embed">
    <iframe
        src="https://www.youtube-nocookie.com/embed/gPRrt_0NMKE"
        allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
        webkitallowfullscreen
        mozallowfullscreen
        allowfullscreen
    >
    </iframe>
    
    <figcaption>2022.04.07 rolling balls motion effects</figcaption>
    
</figure>
<figure class="yt-embed">
    <iframe
        src="https://www.youtube-nocookie.com/embed/hHIv2yO9DvU"
        allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
        webkitallowfullscreen
        mozallowfullscreen
        allowfullscreen
    >
    </iframe>
    
    <figcaption>2022.04.29 cloth sim</figcaption>
    
</figure>
<figure class="yt-embed">
    <iframe
        src="https://www.youtube-nocookie.com/embed/zqyv7GBTLGA"
        allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
        webkitallowfullscreen
        mozallowfullscreen
        allowfullscreen
    >
    </iframe>
    
    <figcaption>2022.05.30 fire handwriting</figcaption>
    
</figure>
<figure class="06 24" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;RB4ih_pdg3IW.webp" alt="a cylinder with a bunch of bumps on it"  />
    </div>
    
    <figcaption><p>2022.06.24 the cylinder</p>
</figcaption>
    
</figure>
<figure class="yt-embed">
    <iframe
        src="https://www.youtube-nocookie.com/embed/XVyMUROofZ8"
        allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
        webkitallowfullscreen
        mozallowfullscreen
        allowfullscreen
    >
    </iframe>
    
    <figcaption>2022.07.21 the iconic donut</figcaption>
    
</figure>
<figure class="yt-embed">
    <iframe
        src="https://www.youtube-nocookie.com/embed/ZGrNNnujR3o"
        allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
        webkitallowfullscreen
        mozallowfullscreen
        allowfullscreen
    >
    </iframe>
    
    <figcaption>2022.07.25 Appletree SMP minecraft animation</figcaption>
    
</figure>
<figure class="yt-embed">
    <iframe
        src="https://www.youtube-nocookie.com/embed/zRlgWbW1Qcw"
        allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
        webkitallowfullscreen
        mozallowfullscreen
        allowfullscreen
    >
    </iframe>
    
    <figcaption>2022.08.01 mirror physics</figcaption>
    
</figure>
<figure class="08 30" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;oi58Ag23Pdmf.webp" alt="the earth from space"  />
    </div>
    
    <figcaption><p>2022.08.30 the earth</p>
</figcaption>
    
</figure>
<figure class="08 31" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;_s4ZYuQv9YLQ.webp" alt="11 glowing pendulums swinging in a flowing curve"  />
    </div>
    
    <figcaption><p>2022.08.31 glowing pendulums</p>
</figcaption>
    
</figure>
<figure class="10 22" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;4LrJwRD-0bPo.webp" alt="an orange flower in a flower pot with skyline dirt"  />
    </div>
    
    <figcaption><p>2022.10.22 orange flower</p>
</figcaption>
    
</figure>
<h2 id="2023">2023</h2>
<figure class="yt-embed">
    <iframe
        src="https://www.youtube-nocookie.com/embed/qnbGPErmmoI"
        allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
        webkitallowfullscreen
        mozallowfullscreen
        allowfullscreen
    >
    </iframe>
    
    <figcaption>2023.11.07 FIRST Digital Animation Award submission</figcaption>
    
</figure>
]]></content:encoded>
    </item>
    
    <item>
      <title>Determining the properties of a spherical mirror with ray diagrams</title>
      <link>https://dunkirk.sh/blog/spherical-ray-diagrams/</link>
      <guid isPermaLink="true">https://dunkirk.sh/blog/spherical-ray-diagrams/</guid>
      <pubDate>Wed, 26 Feb 2025 00:00:00 +0000</pubDate>
      
      
      <dc:creator>Kieran Klukas</dc:creator>
      
      
        
      <category>tool</category>
        
      <category>fancy</category>
        
      <category>physics</category>
        
      
      <description>
        
          &amp;lt;p&amp;gt;I was recently working through the Geometric Optics section of my physics textbook and was having trouble drawing all the ray diagrams (my wrist is still in a cast though that should come off in a few weeks) so I decided to try and make a tool to make them for me instead! I rather expected this to be a fairly simple process but instead it ended up being one of the most math intensive, most difficult — and also most rewarding — projects I’ve made recently!&amp;lt;&#x2F;p&amp;gt;
        
      </description>
      <content:encoded><![CDATA[<p>I was recently working through the Geometric Optics section of my physics textbook and was having trouble drawing all the ray diagrams (my wrist is still in a cast though that should come off in a few weeks) so I decided to try and make a tool to make them for me instead! I rather expected this to be a fairly simple process but instead it ended up being one of the most math intensive, most difficult — and also most rewarding — projects I’ve made recently!</p>
<span id="continue-reading"></span><h2 id="the-tool-drum-roll-please">the tool (🥁 roll please)</h2>
<blockquote>
<p>this tool does support keyboard navigation btw ^-^<br />
<code>arrow keys</code> to move and <code>+</code> and <code>-</code> to zoom</p>
</blockquote>
<div
    id="rayTracer"
    style="display: flex; flex-direction: column; min-height: 40rem"
>
    <div class="controls" style="display: flex; flex-direction: column">
        <div style="display: flex; gap: 20px; align-items: center">
            <div>
                <label>Mirror Type:</label>
                <select id="mirrorType">
                    <option value="concave">Concave Mirror</option>
                    <option value="convex">Convex Mirror</option>
                </select>
            </div>
            <div>
                <label>Radius of Curvature:</label>
                <input
                    type="number"
                    id="radius"
                    value="20"
                    min="0.2"
                    step="0.2"
                />
            </div>
            <div>
                <label>Object Distance:</label>
                <input
                    type="number"
                    id="objectDist"
                    value="30"
                    min="0.2"
                    step="0.2"
                />
            </div>
        </div>
        <div style="display: flex; gap: 20px; align-items: center; width: 100%">
            <div>
                <label>Object Height:</label>
                <input
                    type="number"
                    id="objectHeight"
                    value="20"
                    min="0.1"
                    step="0.1"
                />
            </div>
            <div style="flex: 1">
                <label>Zoom:</label>
                <input
                    type="range"
                    id="zoom"
                    min="0.01"
                    max="8"
                    step="0.01"
                    value="1"
                    style="width: 100%"
                />
            </div>
        </div>
    </div>
    <canvas id="canvas" style="flex: 1; cursor: move"></canvas>
</div>

<style>
    #rayTracer {
        padding: 20px;
    }
    .controls {
        margin-bottom: 20px;
    }
    .controls div {
        margin: 0.2rem 0;
    }
    #canvas {
        border: 1px solid #ccc;
        width: 100%;
    }
</style>

<script>
    const canvas = document.getElementById("canvas");
    const ctx = canvas.getContext("2d");
    const mirrorType = document.getElementById("mirrorType");
    const radiusInput = document.getElementById("radius");
    const objectDistInput = document.getElementById("objectDist");
    const objectHeightInput = document.getElementById("objectHeight");
    const zoomInput = document.getElementById("zoom");

    let offsetX = 0;
    let offsetY = 0;
    let isDragging = false;
    let lastX = 0;
    let lastY = 0;

    canvas.addEventListener("mousedown", (e) => {
        isDragging = true;
        lastX = e.clientX;
        lastY = e.clientY;
    });

    canvas.addEventListener("mousemove", (e) => {
        if (isDragging) {
            offsetX += e.clientX - lastX;
            offsetY += e.clientY - lastY;
            lastX = e.clientX;
            lastY = e.clientY;
            update();
        }
    });

    canvas.addEventListener("mouseup", () => {
        isDragging = false;
    });

    canvas.addEventListener("mouseleave", () => {
        isDragging = false;
    });

    canvas.addEventListener("wheel", (e) => {
        e.preventDefault();
        const zoomSpeed = 0.001;
        const newZoom = parseFloat(zoomInput.value) - e.deltaY * zoomSpeed;
        zoomInput.value = Math.min(Math.max(newZoom, 0.01), 8);
        update();
    });

    function calculateReflectedRay(
        startX,
        startY,
        incidentX,
        incidentY,
        centerX,
        centerY,
        radius,
    ) {
        // Calculate normal vector at intersection point
        const nx = (incidentX - centerX) / radius;
        const ny = (incidentY - centerY) / radius;

        // Calculate incident vector
        const ix = incidentX - startX;
        const iy = incidentY - startY;
        const iLen = Math.sqrt(ix * ix + iy * iy);
        const dirX = ix / iLen;
        const dirY = iy / iLen;

        // Calculate reflection using r = i - 2(i·n)n
        const dot = dirX * nx + dirY * ny;
        const reflectX = dirX - 2 * dot * nx;
        const reflectY = dirY - 2 * dot * ny;

        // Extend reflected ray to edge of canvas
        const t = Math.max(
            Math.abs((0 - incidentX) / reflectX),
            Math.abs((canvas.width - incidentX) / reflectX),
            Math.abs((0 - incidentY) / reflectY),
            Math.abs((canvas.height - incidentY) / reflectY),
        );

        return {
            x: incidentX + reflectX * t,
            y: incidentY + reflectY * t,
        };
    }

    function drawMirror(isConcave, R) {
        const scale = (canvas.width / (R * 6)) * parseFloat(zoomInput.value);
        const centerX = canvas.width / 2 + R * scale * isConcave + offsetX;
        const centerY = canvas.height / 2 + offsetY;

        ctx.beginPath();
        ctx.strokeStyle = "black";
        if (isConcave) {
            ctx.arc(
                centerX - R * scale,
                centerY,
                R * scale,
                -Math.PI / 2.75,
                Math.PI / 2.75,
            );
        } else {
            ctx.arc(
                centerX + R * scale,
                centerY,
                R * scale,
                -Math.PI / 2.75 + Math.PI,
                Math.PI / 2.75 + Math.PI,
            );
        }
        ctx.stroke();
    }

    function drawArrow(x, y, height) {
        const arrowHeadSize = height * 0.1; // Scale arrow head with height
        ctx.lineWidth = 1;
        ctx.beginPath();

        // Draw the main shaft
        ctx.moveTo(x, y);
        ctx.lineTo(x, y - height * 0.9);

        // Draw the arrow head
        ctx.moveTo(x, y - height);
        ctx.lineTo(x - arrowHeadSize, y - height + arrowHeadSize);
        ctx.moveTo(x, y - height);
        ctx.lineTo(x + arrowHeadSize, y - height + arrowHeadSize);
        ctx.moveTo(x - arrowHeadSize, y - height + arrowHeadSize);
        ctx.lineTo(x + arrowHeadSize, y - height + arrowHeadSize);

        ctx.stroke();
    }

    function extendRayToCanvasEdge(x1, y1, x2, y2) {
        const rayDirX = x2 - x1;
        const rayDirY = y2 - y1;
        const t = Math.max(
            Math.abs((0 - x2) / rayDirX),
            Math.abs((canvas.width - x2) / rayDirX),
            Math.abs((0 - y2) / rayDirY),
            Math.abs((canvas.height - y2) / rayDirY),
        );
        ctx.lineTo(x2 + rayDirX * t, y2 + rayDirY * t);
    }

    function findCircleIntersection(radius, x1, h, x3, y3, centerX, centerY) {
        // Check if the input values are valid
        if (radius <= 0) {
            throw new Error("Invalid input values.");
        }

        // Calculate the slope of the line from (x1, h) to (x3, y3)
        const m = (y3 - (centerY - h)) / (x3 - x1);

        // Define the line equation: y = h + m * (x - x1)
        // Substitute into circle equation: (x-centerX)^2 + (y-centerY)^2 = radius^2
        // y = h + m * (x - x1)
        // (x-centerX)^2 + (h + m*(x-x1) - centerY)^2 = radius^2

        // Coefficients for the quadratic equation
        const a = 1 + m * m;
        const b = -2 * centerX + 2 * m * (centerY - h - centerY - m * x1);
        const c =
            centerX * centerX +
            (centerY - h - centerY - m * x1) *
                (centerY - h - centerY - m * x1) -
            radius * radius;

        // Calculate the discriminant
        const discriminant = b * b - 4 * a * c;

        if (discriminant < 0) {
            throw new Error("No intersection found.");
        }

        // Calculate the two possible x values
        const xIntersect1 = (-b + Math.sqrt(discriminant)) / (2 * a);
        const xIntersect2 = (-b - Math.sqrt(discriminant)) / (2 * a);

        // Calculate the corresponding y values
        const yIntersect1 = centerY - h + m * (xIntersect1 - x1);
        const yIntersect2 = centerY - h + m * (xIntersect2 - x1);

        // Return the intersection points
        return [
            { x: xIntersect1, y: yIntersect1 },
            { x: xIntersect2, y: yIntersect2 },
        ];
    }

    function drawRays(isConcave, R, objDist) {
        const scale = (canvas.width / (R * 6)) * parseFloat(zoomInput.value);
        const F = R / 2;
        const h = parseFloat(objectHeightInput.value) * scale;
        const centerX = canvas.width / 2 + R * scale + offsetX;
        const centerY = canvas.height / 2 + offsetY;
        const objX =
            centerX +
            objDist * scale * (isConcave ? -1 : -1) -
            R * scale * !isConcave;
        const objY = centerY;

        drawArrow(objX, objY, h);

        ctx.beginPath();
        ctx.moveTo(0, centerY);
        ctx.lineTo(canvas.width, centerY);
        ctx.stroke();

        ctx.fillStyle = "red";
        ctx.beginPath();
        ctx.arc(centerX - F * scale, centerY, 3, 0, 2 * Math.PI);
        ctx.fill();

        ctx.fillStyle = "blue";
        ctx.beginPath();
        ctx.arc(centerX - R * scale * isConcave, centerY, 3, 0, 2 * Math.PI);
        ctx.fill();

        const circleCenterX = isConcave
            ? centerX - R * scale
            : centerX - R * scale;

        if (isConcave) {
            // ray that travels from the top of the object towards the mirror and then calculating the bounce angle it goes in that direction
            ctx.strokeStyle = "green";
            ctx.beginPath();
            ctx.lineTo(objX, objY - h);
            let intersectionX =
                Math.sqrt((R * scale) ** 2 - h ** 2) + circleCenterX;
            ctx.lineTo(intersectionX, objY - h);
            extendRayToCanvasEdge(
                intersectionX,
                objY - h,
                centerX - F * scale,
                centerY,
            );
            ctx.stroke();

            // draw an extension of the ray through the mirror in a slightly opacified color
            ctx.strokeStyle = "rgba(0, 128, 0, 0.5)";
            ctx.beginPath();
            ctx.lineTo(intersectionX, objY - h);
            extendRayToCanvasEdge(
                centerX - F * scale,
                centerY,
                intersectionX,
                objY - h,
            );
            ctx.stroke();

            // draw a point at the intersection of the ray and the mirror
            ctx.fillStyle = "black";
            ctx.beginPath();
            ctx.arc(intersectionX, objY - h, 3, 0, 2 * Math.PI);
            ctx.fill();

            // draw a ray that travels from the top of the object towards the focal point of the mirror and through the focal point till it reaches the mirror
            ctx.strokeStyle = "purple";
            ctx.beginPath();
            ctx.lineTo(objX, objY - h);
            ctx.lineTo(centerX - F * scale, centerY);
            const extendedRay2 = findCircleIntersection(
                R * scale,
                objX,
                h,
                centerX - F * scale,
                centerY,
                circleCenterX,
                centerY,
            );
            ctx.lineTo(extendedRay2[0].x, extendedRay2[0].y);
            ctx.lineTo(0, extendedRay2[0].y);
            ctx.stroke();

            // draw an extension of the ray through the mirror in a slightly opacified color
            ctx.strokeStyle = "rgba(128, 0, 128, 0.5)";
            ctx.beginPath();
            ctx.lineTo(extendedRay2[0].x, extendedRay2[0].y);
            ctx.lineTo(canvas.width, extendedRay2[0].y);
            ctx.stroke();

            // draw a point at the intersection of the ray and the mirror
            ctx.fillStyle = "black";
            ctx.beginPath();
            ctx.arc(extendedRay2[0].x, extendedRay2[0].y, 3, 0, 2 * Math.PI);
            ctx.fill();

            // draw a ray that travels from the top of the object through the radius of curvature of the mirror
            ctx.strokeStyle = "orange";
            ctx.beginPath();
            ctx.lineTo(objX, objY - h);
            ctx.lineTo(circleCenterX, centerY);
            const extendedRay3 = findCircleIntersection(
                R * scale,
                objX,
                h,
                circleCenterX,
                centerY,
                circleCenterX,
                centerY,
            );
            ctx.lineTo(extendedRay3[0].x, extendedRay3[0].y);
            extendRayToCanvasEdge(
                extendedRay3[0].x,
                extendedRay3[0].y,
                centerX - R * scale,
                centerY,
            );
            ctx.stroke();

            // draw an extension of the ray through the mirror in a slightly opacified color
            ctx.strokeStyle = "rgba(255, 165, 0, 0.5)";
            ctx.beginPath();
            ctx.lineTo(extendedRay3[0].x, extendedRay3[0].y);
            extendRayToCanvasEdge(
                centerX - R * scale,
                centerY,
                extendedRay3[0].x,
                extendedRay3[0].y,
            );
            ctx.stroke();

            // draw a point at the intersection of the ray and the mirror
            ctx.fillStyle = "black";
            ctx.beginPath();
            ctx.arc(extendedRay3[0].x, extendedRay3[0].y, 3, 0, 2 * Math.PI);
            ctx.fill();
        } else {
            // draw a ray that travels from the top of the object horizontally towards the mirror
            ctx.strokeStyle = "green";
            ctx.beginPath();
            ctx.lineTo(objX, objY - h);
            ctx.lineTo(
                centerX - Math.sqrt((R * scale) ** 2 - h ** 2),
                objY - h,
            );
            extendRayToCanvasEdge(
                centerX - F * scale,
                centerY,
                centerX - Math.sqrt((R * scale) ** 2 - h ** 2),
                objY - h,
            );
            ctx.stroke();

            // draw an extension of the ray through the mirror in a slightly opacified color
            ctx.strokeStyle = "rgba(0, 128, 0, 0.5)";
            ctx.beginPath();
            ctx.lineTo(
                centerX - Math.sqrt((R * scale) ** 2 - h ** 2),
                objY - h,
            );
            ctx.lineTo(centerX - F * scale, centerY);
            ctx.stroke();

            // draw a point at the intersection of the ray and the mirror
            ctx.fillStyle = "black";
            ctx.beginPath();
            ctx.arc(
                centerX - Math.sqrt((R * scale) ** 2 - h ** 2),
                objY - h,
                3,
                0,
                2 * Math.PI,
            );
            ctx.fill();

            // draw a ray that travels from the top of the object towards the focal point of the mirror and through the focal point till it reaches the mirror
            ctx.strokeStyle = "purple";
            ctx.beginPath();
            ctx.lineTo(objX, objY - h);
            const extendedRay2 = findCircleIntersection(
                R * scale,
                objX,
                h,
                centerX - F * scale,
                centerY,
                circleCenterX,
                centerY,
            );
            const extendedRay2Y = centerY - (extendedRay2[0].y - centerY);
            ctx.lineTo(
                centerX -
                    Math.sqrt(
                        (R * scale) ** 2 - (centerY - extendedRay2Y) ** 2,
                    ),
                centerY - (extendedRay2[0].y - centerY),
            );
            ctx.lineTo(0, centerY - (extendedRay2[0].y - centerY));
            ctx.stroke();

            // draw an extension of the ray through the mirror in a slightly opacified color
            ctx.strokeStyle = "rgba(128, 0, 128, 0.5)";
            ctx.beginPath();
            ctx.lineTo(
                centerX -
                    Math.sqrt(
                        (R * scale) ** 2 - (centerY - extendedRay2Y) ** 2,
                    ),
                centerY - (extendedRay2[0].y - centerY),
            );
            ctx.lineTo(canvas.width, centerY - (extendedRay2[0].y - centerY));
            ctx.stroke();

            ctx.fillStyle = "black";
            ctx.beginPath();
            ctx.arc(
                centerX -
                    Math.sqrt(
                        (R * scale) ** 2 - (centerY - extendedRay2Y) ** 2,
                    ),
                centerY - (extendedRay2[0].y - centerY),
                3,
                0,
                2 * Math.PI,
            );
            ctx.fill();

            // draw a ray that travels from the top of the object through the radius of curvature of the mirror
            ctx.strokeStyle = "orange";
            ctx.beginPath();
            ctx.lineTo(objX, objY - h);
            // ctx.lineTo(centerX, centerY);
            const extendedRay3ScaleFactor =
                (R * scale) / Math.abs(objX - centerX);
            ctx.lineTo(
                centerX -
                    Math.sqrt(
                        (R * scale) ** 2 - (h * extendedRay3ScaleFactor) ** 2,
                    ),
                centerY - h * extendedRay3ScaleFactor,
            );
            extendRayToCanvasEdge(
                centerX -
                    Math.sqrt(
                        (R * scale) ** 2 - (h * extendedRay3ScaleFactor) ** 2,
                    ),
                centerY - h * extendedRay3ScaleFactor,
                objX,
                objY - h,
            );
            ctx.stroke();

            // draw an extension of the ray through the mirror in a slightly opacified color
            ctx.strokeStyle = "rgba(255, 165, 0, 0.5)";
            ctx.beginPath();
            ctx.lineTo(
                centerX -
                    Math.sqrt(
                        (R * scale) ** 2 - (h * extendedRay3ScaleFactor) ** 2,
                    ),
                centerY - h * extendedRay3ScaleFactor,
            );
            extendRayToCanvasEdge(
                centerX -
                    Math.sqrt(
                        (R * scale) ** 2 - (h * extendedRay3ScaleFactor) ** 2,
                    ),
                centerY - h * extendedRay3ScaleFactor,
                centerX,
                centerY,
            );
            ctx.stroke();

            // draw a point at the intersection of the ray and the mirror

            ctx.fillStyle = "black";
            ctx.beginPath();
            ctx.arc(
                centerX -
                    Math.sqrt(
                        (R * scale) ** 2 - (h * extendedRay3ScaleFactor) ** 2,
                    ),
                centerY - h * extendedRay3ScaleFactor,
                3,
                0,
                2 * Math.PI,
            );
            ctx.fill();

            // draw an extension of the ray through the mirror in a slightly opacified color
            ctx.strokeStyle = "rgba(255, 165, 0, 0.5)";
            ctx.beginPath();
            ctx.lineTo(
                centerX -
                    Math.sqrt(
                        (R * scale) ** 2 - (centerY - extendedRay3Y) ** 2,
                    ),
                centerY - (extendedRay3[0].y - centerY),
            );
            extendRayToCanvasEdge(
                centerX - R * scale,
                centerY,
                centerX -
                    Math.sqrt(
                        (R * scale) ** 2 - (centerY - extendedRay3Y) ** 2,
                    ),
                centerY - (extendedRay3[0].y - centerY),
            );
            ctx.stroke();
        }
    }

    function update() {
        canvas.width = canvas.offsetWidth;
        canvas.height = canvas.offsetHeight;
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        ctx.fillStyle = "#f0f0f0";
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        const isConcave = mirrorType.value === "concave";
        const R = parseFloat(radiusInput.value);
        const objDist = parseFloat(objectDistInput.value);

        drawMirror(isConcave, R);
        drawRays(isConcave, R, objDist);
    }

    function resetHeight() {
        objectHeightInput.value = Math.max(
            parseFloat(((radiusInput.value * 2) / 3).toFixed(2)),
            0.1,
        );
    }

    mirrorType.addEventListener("change", update);
    radiusInput.addEventListener("input", () => {
        resetHeight();
        update();
    });
    objectDistInput.addEventListener("input", update);
    objectHeightInput.addEventListener("input", update);
    zoomInput.addEventListener("input", update);
    window.addEventListener("resize", update);

    resetHeight();
    update();

    let isCanvasHovered = false;

    canvas.addEventListener("mouseenter", () => {
        isCanvasHovered = true;
    });

    canvas.addEventListener("mouseleave", () => {
        isCanvasHovered = false;
    });

    document.addEventListener("keydown", (e) => {
        if (!isCanvasHovered) return;
        if (e.key === "+" || e.key === "=") {
            zoomInput.value = Math.min(parseFloat(zoomInput.value) + 0.1, 8);
            update();
        }
        if (e.key === "-" || e.key === "_") {
            zoomInput.value = Math.max(parseFloat(zoomInput.value) - 0.1, 0.1);
            update();
        }

        // translate the canvas
        if (e.key === "ArrowUp") {
            offsetY -= 25;
            update();
        }
        if (e.key === "ArrowDown") {
            offsetY += 25;
            update();
        }
        if (e.key === "ArrowLeft") {
            offsetX -= 25;
            update();
        }
        if (e.key === "ArrowRight") {
            offsetX += 25;
            update();
        }
    });
</script>
<h2 id="the-math">the math</h2>
<p>I was able to make it a bit simpler by restricting the domain of this tool to spherical mirrors (the only type used in this Module of my physics textbook) but I did tackle both concave and convex mirrors. It generates 3 rays: a horizontal ray, a ray through the focal point, and a ray through the radius of curvature. The first and last are quite easy to generate but the third was a bit more difficult. I ended up using a formula that I don’t quite understand to get the point on the mirror where the ray intersects but it does work so 🤷.</p>
<p>The horizontal ray was dead simple. Draw a line from the top of the arrow to the edge of the mirror and then draw another line from focal point through the intersection point in the mirror. The part of that ray that is behind the mirror is simply the extension of the ray for virtual images but the part in front of the mirror is the actual path of the ray.</p>
<pre class="giallo z-code"><code data-lang="javascript"><span class="giallo-l"><span class="z-punctuation z-definition z-comment z-comment">// Draw the horizontal ray</span></span>
<span class="giallo-l"><span class="z-source">ctx</span><span class="z-punctuation z-accessor">.</span><span class="z-variable z-other z-property z-js">strokeStyle</span><span class="z-keyword z-operator"> =</span><span class="z-punctuation z-definition z-string z-string"> &quot;green&quot;</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-source">ctx</span><span class="z-punctuation z-accessor">.</span><span class="z-entity z-name z-function">beginPath</span><span class="z-source">()</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-source">ctx</span><span class="z-punctuation z-accessor">.</span><span class="z-entity z-name z-function">lineTo</span><span class="z-source z-variable z-other z-readwrite">(objX</span><span class="z-punctuation">,</span><span class="z-variable z-other z-readwrite"> objY</span><span class="z-keyword z-operator"> -</span><span class="z-variable z-other z-readwrite z-source"> h)</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-storage z-type">let</span><span class="z-variable z-other z-readwrite"> intersectionX</span><span class="z-keyword z-operator"> =</span></span>
<span class="giallo-l"><span class="z-source">    Math</span><span class="z-punctuation z-accessor">.</span><span class="z-entity z-name z-function">sqrt</span><span class="z-source z-variable z-other z-constant z-js">((R</span><span class="z-keyword z-operator"> *</span><span class="z-variable z-other z-readwrite z-source"> scale)</span><span class="z-keyword z-operator"> **</span><span class="z-constant z-numeric"> 2</span><span class="z-keyword z-operator"> -</span><span class="z-variable z-other z-readwrite"> h</span><span class="z-keyword z-operator"> **</span><span class="z-constant z-numeric"> 2</span><span class="z-source">)</span><span class="z-keyword z-operator"> +</span><span class="z-variable z-other z-readwrite"> circleCenterX</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-source">ctx</span><span class="z-punctuation z-accessor">.</span><span class="z-entity z-name z-function">lineTo</span><span class="z-source z-variable z-other z-readwrite">(intersectionX</span><span class="z-punctuation">,</span><span class="z-variable z-other z-readwrite"> objY</span><span class="z-keyword z-operator"> -</span><span class="z-variable z-other z-readwrite z-source"> h)</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">extendRayToCanvasEdge</span><span class="z-source">(</span></span>
<span class="giallo-l"><span class="z-variable z-other z-readwrite">    intersectionX</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-variable z-other z-readwrite">    objY</span><span class="z-keyword z-operator"> -</span><span class="z-variable z-other z-readwrite"> h</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-variable z-other z-readwrite">    centerX</span><span class="z-keyword z-operator"> -</span><span class="z-variable z-other z-constant z-js"> F</span><span class="z-keyword z-operator"> *</span><span class="z-variable z-other z-readwrite"> scale</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-variable z-other z-readwrite">    centerY</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-source">)</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-source">ctx</span><span class="z-punctuation z-accessor">.</span><span class="z-entity z-name z-function">stroke</span><span class="z-source">()</span><span class="z-punctuation">;</span></span></code></pre>
<p>The ray through the radius of curvature was also fairly simple but alot more fun to figure out the math for. Since we know that there is a right angle triange between the arrow, center line, and the radius we can use the pythagorean theorem to find the missing side of the intersection height and then we can use the ratio of the radius to the arrow base to find the proper x offset.</p>
<pre class="giallo z-code"><code data-lang="javascript"><span class="giallo-l"><span class="z-punctuation z-definition z-comment z-comment">// Draw the ray through the radius of curvature</span></span>
<span class="giallo-l"><span class="z-source">ctx</span><span class="z-punctuation z-accessor">.</span><span class="z-variable z-other z-property z-js">strokeStyle</span><span class="z-keyword z-operator"> =</span><span class="z-punctuation z-definition z-string z-string"> &quot;orange&quot;</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-source">ctx</span><span class="z-punctuation z-accessor">.</span><span class="z-entity z-name z-function">beginPath</span><span class="z-source">()</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-source">ctx</span><span class="z-punctuation z-accessor">.</span><span class="z-entity z-name z-function">lineTo</span><span class="z-source z-variable z-other z-readwrite">(objX</span><span class="z-punctuation">,</span><span class="z-variable z-other z-readwrite"> objY</span><span class="z-keyword z-operator"> -</span><span class="z-variable z-other z-readwrite z-source"> h)</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-source">ctx</span><span class="z-punctuation z-accessor">.</span><span class="z-entity z-name z-function">lineTo</span><span class="z-source z-variable z-other z-readwrite">(circleCenterX</span><span class="z-punctuation">,</span><span class="z-variable z-other z-readwrite z-source"> centerY)</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-storage z-type">const</span><span class="z-variable z-other z-constant z-js"> extendedRay3</span><span class="z-keyword z-operator"> =</span><span class="z-entity z-name z-function"> findCircleIntersection</span><span class="z-source">(</span></span>
<span class="giallo-l"><span class="z-variable z-other z-constant z-js">    R</span><span class="z-keyword z-operator"> *</span><span class="z-variable z-other z-readwrite"> scale</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-variable z-other z-readwrite">    objX</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-variable z-other z-readwrite">    h</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-variable z-other z-readwrite">    circleCenterX</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-variable z-other z-readwrite">    centerY</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-variable z-other z-readwrite">    circleCenterX</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-variable z-other z-readwrite">    centerY</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-source">)</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-source">ctx</span><span class="z-punctuation z-accessor">.</span><span class="z-entity z-name z-function">lineTo</span><span class="z-source z-variable z-other z-readwrite">(extendedRay3[</span><span class="z-constant z-numeric">0</span><span class="z-source">]</span><span class="z-punctuation z-accessor">.</span><span class="z-variable z-other z-property z-js">x</span><span class="z-punctuation">,</span><span class="z-variable z-other z-readwrite z-source"> extendedRay3[</span><span class="z-constant z-numeric">0</span><span class="z-source">]</span><span class="z-punctuation z-accessor">.</span><span class="z-variable z-other z-property z-js z-source">y)</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">extendRayToCanvasEdge</span><span class="z-source">(</span></span>
<span class="giallo-l"><span class="z-variable z-other z-readwrite z-source">    extendedRay3[</span><span class="z-constant z-numeric">0</span><span class="z-source">]</span><span class="z-punctuation z-accessor">.</span><span class="z-variable z-other z-property z-js">x</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-variable z-other z-readwrite z-source">    extendedRay3[</span><span class="z-constant z-numeric">0</span><span class="z-source">]</span><span class="z-punctuation z-accessor">.</span><span class="z-variable z-other z-property z-js">y</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-variable z-other z-readwrite">    centerX</span><span class="z-keyword z-operator"> -</span><span class="z-variable z-other z-constant z-js"> R</span><span class="z-keyword z-operator"> *</span><span class="z-variable z-other z-readwrite"> scale</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-variable z-other z-readwrite">    centerY</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-source">)</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-source">ctx</span><span class="z-punctuation z-accessor">.</span><span class="z-entity z-name z-function">stroke</span><span class="z-source">()</span><span class="z-punctuation">;</span></span></code></pre>
<p>The last ray, the one through the focal point, was the most difficult to figure out. I had to do quite a bit of geometry to find where this ray intersects the mirror. To find this intersection point I used a method that finds where a line intersects with a circle by solving a quadratic equation. This was necessary because the mirror is actually just part of a circle, and by finding where the ray intersects with that circle I can then determine if that intersection point is actually on the mirror’s surface.</p>
<pre class="giallo z-code"><code data-lang="javascript"><span class="giallo-l"><span class="z-punctuation z-definition z-comment z-comment">// Draw the ray through the focal point</span></span>
<span class="giallo-l"><span class="z-source">ctx</span><span class="z-punctuation z-accessor">.</span><span class="z-variable z-other z-property z-js">strokeStyle</span><span class="z-keyword z-operator"> =</span><span class="z-punctuation z-definition z-string z-string"> &quot;purple&quot;</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-source">ctx</span><span class="z-punctuation z-accessor">.</span><span class="z-entity z-name z-function">beginPath</span><span class="z-source">()</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-source">ctx</span><span class="z-punctuation z-accessor">.</span><span class="z-entity z-name z-function">lineTo</span><span class="z-source z-variable z-other z-readwrite">(objX</span><span class="z-punctuation">,</span><span class="z-variable z-other z-readwrite"> objY</span><span class="z-keyword z-operator"> -</span><span class="z-variable z-other z-readwrite z-source"> h)</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-source">ctx</span><span class="z-punctuation z-accessor">.</span><span class="z-entity z-name z-function">lineTo</span><span class="z-source z-variable z-other z-readwrite">(centerX</span><span class="z-keyword z-operator"> -</span><span class="z-variable z-other z-constant z-js"> F</span><span class="z-keyword z-operator"> *</span><span class="z-variable z-other z-readwrite"> scale</span><span class="z-punctuation">,</span><span class="z-variable z-other z-readwrite z-source"> centerY)</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-storage z-type">const</span><span class="z-variable z-other z-constant z-js"> extendedRay2</span><span class="z-keyword z-operator"> =</span><span class="z-entity z-name z-function"> findCircleIntersection</span><span class="z-source">(</span></span>
<span class="giallo-l"><span class="z-variable z-other z-constant z-js">    R</span><span class="z-keyword z-operator"> *</span><span class="z-variable z-other z-readwrite"> scale</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-variable z-other z-readwrite">    objX</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-variable z-other z-readwrite">    h</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-variable z-other z-readwrite">    centerX</span><span class="z-keyword z-operator"> -</span><span class="z-variable z-other z-constant z-js"> F</span><span class="z-keyword z-operator"> *</span><span class="z-variable z-other z-readwrite"> scale</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-variable z-other z-readwrite">    centerY</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-variable z-other z-readwrite">    circleCenterX</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-variable z-other z-readwrite">    centerY</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-source">)</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-source">ctx</span><span class="z-punctuation z-accessor">.</span><span class="z-entity z-name z-function">lineTo</span><span class="z-source z-variable z-other z-readwrite">(extendedRay2[</span><span class="z-constant z-numeric">0</span><span class="z-source">]</span><span class="z-punctuation z-accessor">.</span><span class="z-variable z-other z-property z-js">x</span><span class="z-punctuation">,</span><span class="z-variable z-other z-readwrite z-source"> extendedRay2[</span><span class="z-constant z-numeric">0</span><span class="z-source">]</span><span class="z-punctuation z-accessor">.</span><span class="z-variable z-other z-property z-js z-source">y)</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-source">ctx</span><span class="z-punctuation z-accessor">.</span><span class="z-entity z-name z-function">lineTo</span><span class="z-source">(</span><span class="z-constant z-numeric">0</span><span class="z-punctuation">,</span><span class="z-variable z-other z-readwrite z-source"> extendedRay2[</span><span class="z-constant z-numeric">0</span><span class="z-source">]</span><span class="z-punctuation z-accessor">.</span><span class="z-variable z-other z-property z-js z-source">y)</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-source">ctx</span><span class="z-punctuation z-accessor">.</span><span class="z-entity z-name z-function">stroke</span><span class="z-source">()</span><span class="z-punctuation">;</span></span></code></pre>
<p>The method works by taking the equation of the line between our arrow tip and focal point (y = mx + b) and the equation of our mirror’s circle ((x-h)² + (y-k)² = r²) and substituting one into the other. This gives us a quadratic equation that we can solve to find the x coordinates of the intersection points. Once we have these x values, we can plug them back into our line equation to get the y coordinates.</p>
<p>Then we just need to check which of these intersection points is actually on the mirror’s surface (since a line can intersect a circle in up to two points) and use that for our ray. From there, we can draw the reflected ray just like with the other two methods.</p>
<p>I will freely admit that I made heavy use of gpt-4o to figure out the inital equations as thats a bit beyond the current scope of my knowledge. The rest of the ray logic was too complex for gemini or claude to figure out so that bit was all me 😎</p>
<pre class="giallo z-code"><code data-lang="javascript"><span class="giallo-l"><span class="z-punctuation z-definition z-comment z-comment">// fancy complex scary math 👻</span></span>
<span class="giallo-l"><span class="z-storage z-type">function</span><span class="z-entity z-name z-function"> findCircleIntersection</span><span class="z-punctuation">(</span><span class="z-variable z-parameter">radius</span><span class="z-punctuation">,</span><span class="z-variable z-parameter"> x1</span><span class="z-punctuation">,</span><span class="z-variable z-parameter"> h</span><span class="z-punctuation">,</span><span class="z-variable z-parameter"> x3</span><span class="z-punctuation">,</span><span class="z-variable z-parameter"> y3</span><span class="z-punctuation">,</span><span class="z-variable z-parameter"> centerX</span><span class="z-punctuation">,</span><span class="z-variable z-parameter"> centerY</span><span class="z-punctuation">) {</span></span>
<span class="giallo-l"><span class="z-punctuation z-definition z-comment z-comment">    // Check if the input values are valid</span></span>
<span class="giallo-l"><span class="z-keyword">    if</span><span class="z-source z-variable z-other z-readwrite"> (radius</span><span class="z-keyword z-operator"> &lt;=</span><span class="z-constant z-numeric"> 0</span><span class="z-source">)</span><span class="z-punctuation"> {</span></span>
<span class="giallo-l"><span class="z-keyword">        throw</span><span class="z-keyword z-operator z-new"> new</span><span class="z-entity z-name z-function"> Error</span><span class="z-source">(</span><span class="z-punctuation z-definition z-string z-string">&quot;Invalid input values.&quot;</span><span class="z-source">)</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-punctuation">    }</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-punctuation z-definition z-comment z-comment">    // Calculate the slope of the line from (x1, h) to (x3, y3)</span></span>
<span class="giallo-l"><span class="z-storage z-type">    const</span><span class="z-variable z-other z-constant z-js"> m</span><span class="z-keyword z-operator"> =</span><span class="z-source z-variable z-other z-readwrite"> (y3</span><span class="z-keyword z-operator"> -</span><span class="z-source z-variable z-other z-readwrite"> (centerY</span><span class="z-keyword z-operator"> -</span><span class="z-variable z-other z-readwrite z-source"> h))</span><span class="z-keyword z-operator"> /</span><span class="z-source z-variable z-other z-readwrite"> (x3</span><span class="z-keyword z-operator"> -</span><span class="z-variable z-other z-readwrite z-source"> x1)</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-punctuation z-definition z-comment z-comment">    // Define the line equation: y = h + m * (x - x1)</span></span>
<span class="giallo-l"><span class="z-punctuation z-definition z-comment z-comment">    // Substitute into circle equation: (x-centerX)^2 + (y-centerY)^2 = radius^2</span></span>
<span class="giallo-l"><span class="z-punctuation z-definition z-comment z-comment">    // y = h + m * (x - x1)</span></span>
<span class="giallo-l"><span class="z-punctuation z-definition z-comment z-comment">    // (x-centerX)^2 + (h + m*(x-x1) - centerY)^2 = radius^2</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-punctuation z-definition z-comment z-comment">    // Coefficients for the quadratic equation</span></span>
<span class="giallo-l"><span class="z-storage z-type">    const</span><span class="z-variable z-other z-constant z-js"> a</span><span class="z-keyword z-operator"> =</span><span class="z-constant z-numeric"> 1</span><span class="z-keyword z-operator"> +</span><span class="z-variable z-other z-readwrite"> m</span><span class="z-keyword z-operator"> *</span><span class="z-variable z-other z-readwrite"> m</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-storage z-type">    const</span><span class="z-variable z-other z-constant z-js"> b</span><span class="z-keyword z-operator"> = -</span><span class="z-constant z-numeric">2</span><span class="z-keyword z-operator"> *</span><span class="z-variable z-other z-readwrite"> centerX</span><span class="z-keyword z-operator"> +</span><span class="z-constant z-numeric"> 2</span><span class="z-keyword z-operator"> *</span><span class="z-variable z-other z-readwrite"> m</span><span class="z-keyword z-operator"> *</span><span class="z-source z-variable z-other z-readwrite"> (centerY</span><span class="z-keyword z-operator"> -</span><span class="z-variable z-other z-readwrite"> h</span><span class="z-keyword z-operator"> -</span><span class="z-variable z-other z-readwrite"> centerY</span><span class="z-keyword z-operator"> -</span><span class="z-variable z-other z-readwrite"> m</span><span class="z-keyword z-operator"> *</span><span class="z-variable z-other z-readwrite z-source"> x1)</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-storage z-type">    const</span><span class="z-variable z-other z-constant z-js"> c</span><span class="z-keyword z-operator"> =</span></span>
<span class="giallo-l"><span class="z-variable z-other z-readwrite">        centerX</span><span class="z-keyword z-operator"> *</span><span class="z-variable z-other z-readwrite"> centerX</span><span class="z-keyword z-operator"> +</span></span>
<span class="giallo-l"><span class="z-source z-variable z-other z-readwrite">        (centerY</span><span class="z-keyword z-operator"> -</span><span class="z-variable z-other z-readwrite"> h</span><span class="z-keyword z-operator"> -</span><span class="z-variable z-other z-readwrite"> centerY</span><span class="z-keyword z-operator"> -</span><span class="z-variable z-other z-readwrite"> m</span><span class="z-keyword z-operator"> *</span><span class="z-variable z-other z-readwrite z-source"> x1)</span><span class="z-keyword z-operator"> *</span></span>
<span class="giallo-l"><span class="z-source z-variable z-other z-readwrite">            (centerY</span><span class="z-keyword z-operator"> -</span><span class="z-variable z-other z-readwrite"> h</span><span class="z-keyword z-operator"> -</span><span class="z-variable z-other z-readwrite"> centerY</span><span class="z-keyword z-operator"> -</span><span class="z-variable z-other z-readwrite"> m</span><span class="z-keyword z-operator"> *</span><span class="z-variable z-other z-readwrite z-source"> x1)</span><span class="z-keyword z-operator"> -</span></span>
<span class="giallo-l"><span class="z-variable z-other z-readwrite">        radius</span><span class="z-keyword z-operator"> *</span><span class="z-variable z-other z-readwrite"> radius</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-punctuation z-definition z-comment z-comment">    // Calculate the discriminant</span></span>
<span class="giallo-l"><span class="z-storage z-type">    const</span><span class="z-variable z-other z-constant z-js"> discriminant</span><span class="z-keyword z-operator"> =</span><span class="z-variable z-other z-readwrite"> b</span><span class="z-keyword z-operator"> *</span><span class="z-variable z-other z-readwrite"> b</span><span class="z-keyword z-operator"> -</span><span class="z-constant z-numeric"> 4</span><span class="z-keyword z-operator"> *</span><span class="z-variable z-other z-readwrite"> a</span><span class="z-keyword z-operator"> *</span><span class="z-variable z-other z-readwrite"> c</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-keyword">    if</span><span class="z-source z-variable z-other z-readwrite"> (discriminant</span><span class="z-keyword z-operator"> &lt;</span><span class="z-constant z-numeric"> 0</span><span class="z-source">)</span><span class="z-punctuation"> {</span></span>
<span class="giallo-l"><span class="z-keyword">        throw</span><span class="z-keyword z-operator z-new"> new</span><span class="z-entity z-name z-function"> Error</span><span class="z-source">(</span><span class="z-punctuation z-definition z-string z-string">&quot;No intersection found.&quot;</span><span class="z-source">)</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-punctuation">    }</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-punctuation z-definition z-comment z-comment">    // Calculate the two possible x values</span></span>
<span class="giallo-l"><span class="z-storage z-type">    const</span><span class="z-variable z-other z-constant z-js"> xIntersect1</span><span class="z-keyword z-operator"> =</span><span class="z-source"> (</span><span class="z-keyword z-operator">-</span><span class="z-variable z-other z-readwrite">b</span><span class="z-keyword z-operator"> +</span><span class="z-source"> Math</span><span class="z-punctuation z-accessor">.</span><span class="z-entity z-name z-function">sqrt</span><span class="z-source z-variable z-other z-readwrite">(discriminant))</span><span class="z-keyword z-operator"> /</span><span class="z-source"> (</span><span class="z-constant z-numeric">2</span><span class="z-keyword z-operator"> *</span><span class="z-variable z-other z-readwrite z-source"> a)</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-storage z-type">    const</span><span class="z-variable z-other z-constant z-js"> xIntersect2</span><span class="z-keyword z-operator"> =</span><span class="z-source"> (</span><span class="z-keyword z-operator">-</span><span class="z-variable z-other z-readwrite">b</span><span class="z-keyword z-operator"> -</span><span class="z-source"> Math</span><span class="z-punctuation z-accessor">.</span><span class="z-entity z-name z-function">sqrt</span><span class="z-source z-variable z-other z-readwrite">(discriminant))</span><span class="z-keyword z-operator"> /</span><span class="z-source"> (</span><span class="z-constant z-numeric">2</span><span class="z-keyword z-operator"> *</span><span class="z-variable z-other z-readwrite z-source"> a)</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-punctuation z-definition z-comment z-comment">    // Calculate the corresponding y values</span></span>
<span class="giallo-l"><span class="z-storage z-type">    const</span><span class="z-variable z-other z-constant z-js"> yIntersect1</span><span class="z-keyword z-operator"> =</span><span class="z-variable z-other z-readwrite"> centerY</span><span class="z-keyword z-operator"> -</span><span class="z-variable z-other z-readwrite"> h</span><span class="z-keyword z-operator"> +</span><span class="z-variable z-other z-readwrite"> m</span><span class="z-keyword z-operator"> *</span><span class="z-source z-variable z-other z-readwrite"> (xIntersect1</span><span class="z-keyword z-operator"> -</span><span class="z-variable z-other z-readwrite z-source"> x1)</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-storage z-type">    const</span><span class="z-variable z-other z-constant z-js"> yIntersect2</span><span class="z-keyword z-operator"> =</span><span class="z-variable z-other z-readwrite"> centerY</span><span class="z-keyword z-operator"> -</span><span class="z-variable z-other z-readwrite"> h</span><span class="z-keyword z-operator"> +</span><span class="z-variable z-other z-readwrite"> m</span><span class="z-keyword z-operator"> *</span><span class="z-source z-variable z-other z-readwrite"> (xIntersect2</span><span class="z-keyword z-operator"> -</span><span class="z-variable z-other z-readwrite z-source"> x1)</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-punctuation z-definition z-comment z-comment">    // Return the intersection points</span></span>
<span class="giallo-l"><span class="z-keyword">    return</span><span class="z-source"> [</span></span>
<span class="giallo-l"><span class="z-punctuation">        {</span><span class="z-source"> x</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-variable z-other z-readwrite"> xIntersect1</span><span class="z-punctuation">,</span><span class="z-source"> y</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-variable z-other z-readwrite"> yIntersect1</span><span class="z-punctuation"> },</span></span>
<span class="giallo-l"><span class="z-punctuation">        {</span><span class="z-source"> x</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-variable z-other z-readwrite"> xIntersect2</span><span class="z-punctuation">,</span><span class="z-source"> y</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-variable z-other z-readwrite"> yIntersect2</span><span class="z-punctuation"> },</span></span>
<span class="giallo-l"><span class="z-source">    ]</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-punctuation">}</span></span></code></pre>]]></content:encoded>
    </item>
    
    <item>
      <title>Cleaning exif data with git pre-commit</title>
      <link>https://dunkirk.sh/blog/remove-exif-git-hook/</link>
      <guid isPermaLink="true">https://dunkirk.sh/blog/remove-exif-git-hook/</guid>
      <pubDate>Sat, 15 Feb 2025 19:57:01 +0000</pubDate>
      
      
      <dc:creator>Kieran Klukas</dc:creator>
      
      
        
      <category>mildrant</category>
        
      <category>tutorial</category>
        
      
      <description>
        
          &amp;lt;p&amp;gt;I saw this &amp;lt;a rel=&amp;quot;noopener external&amp;quot; target=&amp;quot;_blank&amp;quot; href=&amp;quot;https:&#x2F;&#x2F;jade.fyi&#x2F;blog&#x2F;pre-commit-exif-safety&#x2F;&amp;quot;&amp;gt;post&amp;lt;&#x2F;a&amp;gt; from &amp;lt;a rel=&amp;quot;noopener external&amp;quot; target=&amp;quot;_blank&amp;quot; href=&amp;quot;https:&#x2F;&#x2F;jade.fyi&amp;quot;&amp;gt;jade.fyi&amp;lt;&#x2F;a&amp;gt; on using a git hook to clear exif data from your images before you commit them and realized I should probably implement that too lol. Interestingly jade also uses zola for her site but she used pre-commit hooks whereas I wanted to do something that used native git hooks.&amp;lt;&#x2F;p&amp;gt;
        
      </description>
      <content:encoded><![CDATA[<p>I saw this <a rel="noopener external" target="_blank" href="https://jade.fyi/blog/pre-commit-exif-safety/">post</a> from <a rel="noopener external" target="_blank" href="https://jade.fyi">jade.fyi</a> on using a git hook to clear exif data from your images before you commit them and realized I should probably implement that too lol. Interestingly jade also uses zola for her site but she used pre-commit hooks whereas I wanted to do something that used native git hooks.</p>
<span id="continue-reading"></span>
<p>I started with the naive method of just having a <code>.git/hooks/pre-commit</code> file that would run <code>exiftool</code> on the input but after realizing that hooks placed there wouldn’t be synced to the repo decided that wasn’t the best way. I moved to using a script that would symlink files from the <code>hooks</code> directory to <code>.git/hooks</code>. It worked moderately well but due to the fact that I used (yes I feel the shame admitting this <a rel="noopener external" target="_blank" href="https://cachet.dunkirk.sh/emojis/uw_embarrassed/r">:uw_embarrassed:</a>) <code>#!/bin/bash</code> instead of <code>#!/usr/bin/env bash</code>. Not realizing my mistake and believing it to be related to the symlink I found <a rel="noopener external" target="_blank" href="https://stackoverflow.com/questions/4592838/symbolic-link-to-a-hook-in-git/#:~:text=While%20you%20can%20use%20symbolic%20links">this stack overflow</a> answer which taught me that you can use <code>git config core.hooksPath hooks</code> to move the hooks directory to <code>./hooks</code> in the root of your repo! After doing that and it still not working (i feel very dense writing this lol) I finally realized that the shebang was wrong and then it worked!</p>
<figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;Q-zmdBNx9Bee.webp" alt="the commit hook finally working!"  />
    </div>
    
    <figcaption><p>phew</p>
</figcaption>
    
</figure>
<p>Is there anything at all to learn from this? Well yes actually! You can use the script below and the <code>git config core.hooksPath hooks</code> setting to scrub your own images!</p>
<blockquote>
<p>hooks/pre-commit</p>
</blockquote>
<pre class="giallo z-code"><code data-lang="shellscript"><span class="giallo-l"><span class="z-punctuation z-definition z-comment z-shebang z-shell z-meta z-shebang z-shell">#!/usr/bin/env bash</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-punctuation z-definition z-comment z-comment"># Check if exiftool is installed</span></span>
<span class="giallo-l"><span class="z-keyword">if</span><span class="z-keyword z-operator"> !</span><span class="z-support z-function z-builtin"> command</span><span class="z-string"> -v exiftool</span><span class="z-punctuation"> &amp;</span><span class="z-keyword z-operator">&gt;</span><span class="z-source"> /dev/null</span><span class="z-punctuation">;</span><span class="z-keyword"> then</span></span>
<span class="giallo-l"><span class="z-support z-function z-builtin">  echo</span><span class="z-punctuation z-definition z-string z-string"> &quot;Error: exiftool is not installed.  Please install it.&quot;</span><span class="z-keyword z-operator"> &gt;&amp;2</span></span>
<span class="giallo-l"><span class="z-support z-function z-builtin">  exit</span><span class="z-constant z-numeric"> 1</span></span>
<span class="giallo-l"><span class="z-keyword">fi</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-keyword">while</span><span class="z-support z-function z-builtin"> read</span><span class="z-string"> -r file</span><span class="z-punctuation">;</span><span class="z-keyword"> do</span></span>
<span class="giallo-l"><span class="z-keyword">  case</span><span class="z-punctuation z-definition z-string"> &quot;</span><span class="z-variable z-other z-normal z-shell">$file</span><span class="z-punctuation z-definition z-string">&quot;</span><span class="z-keyword"> in</span></span>
<span class="giallo-l"><span class="z-punctuation">    *</span><span class="z-string">.jpg</span><span class="z-punctuation">|*</span><span class="z-string">.jpeg</span><span class="z-punctuation">|*</span><span class="z-string">.png</span><span class="z-punctuation">|*</span><span class="z-string">.gif</span><span class="z-punctuation">|*</span><span class="z-string">.tiff</span><span class="z-punctuation">|*</span><span class="z-string">.bmp</span><span class="z-keyword z-operator">)</span></span>
<span class="giallo-l"><span class="z-support z-function z-builtin">      echo</span><span class="z-punctuation z-definition z-string z-string"> &quot;Removing EXIF data from: </span><span class="z-variable z-other z-normal z-shell">$file</span><span class="z-punctuation z-definition z-string">&quot;</span><span class="z-keyword z-operator"> &gt;&amp;2</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">      exiftool</span><span class="z-string z-punctuation z-definition z-string"> -all= --icc_profile:all -tagsfromfile @ -orientation -overwrite_original &quot;</span><span class="z-variable z-other z-normal z-shell">$file</span><span class="z-punctuation z-definition z-string">&quot;</span></span>
<span class="giallo-l"><span class="z-keyword">      if</span><span class="z-punctuation"> [</span><span class="z-punctuation z-definition z-variable z-source"> $?</span><span class="z-keyword z-operator"> -ne</span><span class="z-constant z-numeric"> 0</span><span class="z-punctuation"> ];</span><span class="z-keyword"> then</span></span>
<span class="giallo-l"><span class="z-support z-function z-builtin">        echo</span><span class="z-punctuation z-definition z-string z-string"> &quot;Error: exiftool failed to process </span><span class="z-variable z-other z-normal z-shell">$file</span><span class="z-punctuation z-definition z-string">&quot;</span><span class="z-keyword z-operator"> &gt;&amp;2</span></span>
<span class="giallo-l"><span class="z-support z-function z-builtin">        exit</span><span class="z-constant z-numeric"> 1</span></span>
<span class="giallo-l"><span class="z-keyword">      fi</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">      git</span><span class="z-string z-punctuation z-definition z-string"> add &quot;</span><span class="z-variable z-other z-normal z-shell">$file</span><span class="z-punctuation z-definition z-string">&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">      ;;</span></span>
<span class="giallo-l"><span class="z-keyword z-operator">    *)</span></span>
<span class="giallo-l"><span class="z-punctuation">      ;;</span></span>
<span class="giallo-l"><span class="z-keyword">  esac</span></span>
<span class="giallo-l"><span class="z-keyword">done</span><span class="z-keyword z-operator"> &lt;</span><span class="z-punctuation z-definition z-string"> &lt;(</span><span class="z-entity z-name z-function">git</span><span class="z-string z-punctuation z-definition z-string"> diff --cached --name-only --diff-filter=ACMR)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-support z-function z-builtin">exit</span><span class="z-string"> -0</span></span></code></pre>
<blockquote>
<p>if you want to add something or comment on the post then I posted about it on bluesky: <a rel="noopener external" target="_blank" href="https://bsky.app/profile/dunkirk.sh/post/3liaybkkas226">https://bsky.app/profile/dunkirk.sh/post/3liaybkkas226</a></p>
</blockquote>
]]></content:encoded>
    </item>
    
    <item>
      <title>Fixing a degraded zpool on proxmox</title>
      <link>https://dunkirk.sh/blog/degraded-zpool-proxmox/</link>
      <guid isPermaLink="true">https://dunkirk.sh/blog/degraded-zpool-proxmox/</guid>
      <pubDate>Mon, 03 Feb 2025 10:00:00 +0000</pubDate>
      
      
      <dc:creator>Kieran Klukas</dc:creator>
      
      
        
      <category>homelab</category>
        
      <category>tutorial</category>
        
      
      <description>
        
          &amp;lt;p&amp;gt;I decided to finally fix the network issues with my proxmox server (old static ip and used vlans which I hadn’t setup with the new switch and router) as I had some time today but after fixing that fairly easily I discovered that my main &amp;lt;code&amp;gt;2.23 TB&amp;lt;&#x2F;code&amp;gt; zpool had a drive failure. Thankfully I had managed to stuff 3 disks into the case before so loosing one meant no data loss (thankfully 😬; all my projects from the last 5 years as well as my entire video archive is on this pool). I still have 3 more disks of the same type so I can swap in a new one 2 more times after this.&amp;lt;&#x2F;p&amp;gt;
        
      </description>
      <content:encoded><![CDATA[<p>I decided to finally fix the network issues with my proxmox server (old static ip and used vlans which I hadn’t setup with the new switch and router) as I had some time today but after fixing that fairly easily I discovered that my main <code>2.23 TB</code> zpool had a drive failure. Thankfully I had managed to stuff 3 disks into the case before so loosing one meant no data loss (thankfully 😬; all my projects from the last 5 years as well as my entire video archive is on this pool). I still have 3 more disks of the same type so I can swap in a new one 2 more times after this.</p>
<span id="continue-reading"></span><figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;tN1RfSRLAeo2.webp" alt="the zpool reporting a downed disk"  />
    </div>
    
    <figcaption><p>That really scared the pants off me when I first saw it 😂</p>
</figcaption>
    
</figure>
<h2 id="actually-fixing-it">Actually fixing it</h2>
<p>First I had to find the affected disk physically in my case. Because I was stupid I didn’t bother to label them but thankfully the serial numbers of the drives are stuck to them with a sticker so that wasn’t terrible.</p>
<figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;hc-cdn.hel1.your-objectstorage.com&#x2F;s&#x2F;v3&#x2F;a6512def9bbeedbc1315a8ee58c92fbfb9e4d169_0image_from_ios.jpg" alt="chick-fil-a macaroni and cheese with 2 nuggets and some ketchup"  />
    </div>
    
    <figcaption><p>(By this point I had spent 30 minutes moaning so I went to lunch)</p>
</figcaption>
    
</figure>
<p>Now we can run <code>lsblk -o +MODEL,SERIAL</code> to find the serial number of our new drive.</p>
<blockquote>
<p>root@thespia:~# lsblk -o +MODEL,SERIAL</p>
</blockquote>
<pre class="giallo z-code"><code data-lang="shellscript"><span class="giallo-l"><span class="z-entity z-name z-function">NAME</span><span class="z-string">                      MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS MODEL                   SERIAL</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">sda</span><span class="z-string">                         8:0</span><span class="z-constant z-numeric">    0</span><span class="z-string"> 698.6G</span><span class="z-constant z-numeric">  0</span><span class="z-string"> disk             ST3750640NS             3QD0BG0J</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">├─sda1</span><span class="z-string">                      8:1</span><span class="z-constant z-numeric">    0</span><span class="z-string"> 698.6G</span><span class="z-constant z-numeric">  0</span><span class="z-string"> part</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">└─sda9</span><span class="z-string">                      8:9</span><span class="z-constant z-numeric">    0</span><span class="z-string">     8M</span><span class="z-constant z-numeric">  0</span><span class="z-string"> part</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">sdb</span><span class="z-string">                         8:16</span><span class="z-constant z-numeric">   0</span><span class="z-string"> 698.6G</span><span class="z-constant z-numeric">  0</span><span class="z-string"> disk             ST3750640NS             3QD0BN6V</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">sdc</span><span class="z-string">                         8:32</span><span class="z-constant z-numeric">   0</span><span class="z-string"> 698.6G</span><span class="z-constant z-numeric">  0</span><span class="z-string"> disk             ST3750640NS             3QD0BQ5G</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">├─sdc1</span><span class="z-string">                      8:33</span><span class="z-constant z-numeric">   0</span><span class="z-string"> 698.6G</span><span class="z-constant z-numeric">  0</span><span class="z-string"> part</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">└─sdc9</span><span class="z-string">                      8:41</span><span class="z-constant z-numeric">   0</span><span class="z-string">     8M</span><span class="z-constant z-numeric">  0</span><span class="z-string"> part</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">sdd</span><span class="z-string">                         8:48</span><span class="z-constant z-numeric">   1</span><span class="z-string"> 111.8G</span><span class="z-constant z-numeric">  0</span><span class="z-string"> disk             Hitachi HTS543212L9SA02 090130FBEB00LGGJ35RF</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">├─sdd1</span><span class="z-string">                      8:49</span><span class="z-constant z-numeric">   1</span><span class="z-string">  1007K</span><span class="z-constant z-numeric">  0</span><span class="z-string"> part</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">├─sdd2</span><span class="z-string">                      8:50</span><span class="z-constant z-numeric">   1</span><span class="z-string">   512M</span><span class="z-constant z-numeric">  0</span><span class="z-string"> part /boot/efi</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">└─sdd3</span><span class="z-string">                      8:51</span><span class="z-constant z-numeric">   1</span><span class="z-string"> 111.3G</span><span class="z-constant z-numeric">  0</span><span class="z-string"> part</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">  ├─pve-swap</span><span class="z-string">              253:0</span><span class="z-constant z-numeric">    0</span><span class="z-string">     8G</span><span class="z-constant z-numeric">  0</span><span class="z-string"> lvm</span><span class="z-source">  [SWAP]</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">  ├─pve-root</span><span class="z-string">              253:1</span><span class="z-constant z-numeric">    0</span><span class="z-string">  37.8G</span><span class="z-constant z-numeric">  0</span><span class="z-string"> lvm  /</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">  ├─pve-data_tmeta</span><span class="z-string">        253:2</span><span class="z-constant z-numeric">    0</span><span class="z-string">     1G</span><span class="z-constant z-numeric">  0</span><span class="z-string"> lvm</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">  │</span><span class="z-string"> └─pve-data-tpool      253:4</span><span class="z-constant z-numeric">    0</span><span class="z-string">  49.6G</span><span class="z-constant z-numeric">  0</span><span class="z-string"> lvm</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">  │</span><span class="z-string">   ├─pve-data          253:5</span><span class="z-constant z-numeric">    0</span><span class="z-string">  49.6G</span><span class="z-constant z-numeric">  1</span><span class="z-string"> lvm</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">  │</span><span class="z-string">   ├─pve-vm--100--cloudinit</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">  │</span><span class="z-string">   │                   253:6</span><span class="z-constant z-numeric">    0</span><span class="z-string">     4M</span><span class="z-constant z-numeric">  0</span><span class="z-string"> lvm</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">  │</span><span class="z-string">   ├─pve-vm--101--cloudinit</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">  │</span><span class="z-string">   │                   253:7</span><span class="z-constant z-numeric">    0</span><span class="z-string">     4M</span><span class="z-constant z-numeric">  0</span><span class="z-string"> lvm</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">  │</span><span class="z-string">   ├─pve-vm--103--disk--0</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">  │</span><span class="z-string">   │                   253:8</span><span class="z-constant z-numeric">    0</span><span class="z-string">     4M</span><span class="z-constant z-numeric">  0</span><span class="z-string"> lvm</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">  │</span><span class="z-string">   └─pve-vm--103--disk--1</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">  │</span><span class="z-string">                       253:9</span><span class="z-constant z-numeric">    0</span><span class="z-string">    32G</span><span class="z-constant z-numeric">  0</span><span class="z-string"> lvm</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">  └─pve-data_tdata</span><span class="z-string">        253:3</span><span class="z-constant z-numeric">    0</span><span class="z-string">  49.6G</span><span class="z-constant z-numeric">  0</span><span class="z-string"> lvm</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">    └─pve-data-tpool</span><span class="z-string">      253:4</span><span class="z-constant z-numeric">    0</span><span class="z-string">  49.6G</span><span class="z-constant z-numeric">  0</span><span class="z-string"> lvm</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">      ├─pve-data</span><span class="z-string">          253:5</span><span class="z-constant z-numeric">    0</span><span class="z-string">  49.6G</span><span class="z-constant z-numeric">  1</span><span class="z-string"> lvm</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">      ├─pve-vm--100--cloudinit</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">      │</span><span class="z-string">                   253:6</span><span class="z-constant z-numeric">    0</span><span class="z-string">     4M</span><span class="z-constant z-numeric">  0</span><span class="z-string"> lvm</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">      ├─pve-vm--101--cloudinit</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">      │</span><span class="z-string">                   253:7</span><span class="z-constant z-numeric">    0</span><span class="z-string">     4M</span><span class="z-constant z-numeric">  0</span><span class="z-string"> lvm</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">      ├─pve-vm--103--disk--0</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">      │</span><span class="z-string">                   253:8</span><span class="z-constant z-numeric">    0</span><span class="z-string">     4M</span><span class="z-constant z-numeric">  0</span><span class="z-string"> lvm</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">      └─pve-vm--103--disk--1</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">                          253:9</span><span class="z-constant z-numeric">    0</span><span class="z-string">    32G</span><span class="z-constant z-numeric">  0</span><span class="z-string"> lvm</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">sde</span><span class="z-string">                         8:64</span><span class="z-constant z-numeric">   0</span><span class="z-string"> 465.8G</span><span class="z-constant z-numeric">  0</span><span class="z-string"> disk             WDC WD5000AAKS-65YGA0   WD-WCAS83511331</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">├─sde1</span><span class="z-string">                      8:65</span><span class="z-constant z-numeric">   0</span><span class="z-string"> 465.8G</span><span class="z-constant z-numeric">  0</span><span class="z-string"> part</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">└─sde9</span><span class="z-string">                      8:73</span><span class="z-constant z-numeric">   0</span><span class="z-string">     8M</span><span class="z-constant z-numeric">  0</span><span class="z-string"> part</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">sdf</span><span class="z-string">                         8:80</span><span class="z-constant z-numeric">   1</span><span class="z-string">     0B</span><span class="z-constant z-numeric">  0</span><span class="z-string"> disk             Multi-Card</span><span class="z-constant z-numeric">              20120926571200000</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">zd0</span><span class="z-string">                       230:0</span><span class="z-constant z-numeric">    0</span><span class="z-string">    32G</span><span class="z-constant z-numeric">  0</span><span class="z-string"> disk</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">├─zd0p1</span><span class="z-string">                   230:1</span><span class="z-constant z-numeric">    0</span><span class="z-string">   100M</span><span class="z-constant z-numeric">  0</span><span class="z-string"> part</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">├─zd0p2</span><span class="z-string">                   230:2</span><span class="z-constant z-numeric">    0</span><span class="z-string">    16M</span><span class="z-constant z-numeric">  0</span><span class="z-string"> part</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">├─zd0p3</span><span class="z-string">                   230:3</span><span class="z-constant z-numeric">    0</span><span class="z-string">  31.4G</span><span class="z-constant z-numeric">  0</span><span class="z-string"> part</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">└─zd0p4</span><span class="z-string">                   230:4</span><span class="z-constant z-numeric">    0</span><span class="z-string">   522M</span><span class="z-constant z-numeric">  0</span><span class="z-string"> part</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">zd16</span><span class="z-string">                      230:16</span><span class="z-constant z-numeric">   0</span><span class="z-string">    80G</span><span class="z-constant z-numeric">  0</span><span class="z-string"> disk</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">├─zd16p1</span><span class="z-string">                  230:17</span><span class="z-constant z-numeric">   0</span><span class="z-string">     1M</span><span class="z-constant z-numeric">  0</span><span class="z-string"> part</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">└─zd16p2</span><span class="z-string">                  230:18</span><span class="z-constant z-numeric">   0</span><span class="z-string">    80G</span><span class="z-constant z-numeric">  0</span><span class="z-string"> part</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">zd32</span><span class="z-string">                      230:32</span><span class="z-constant z-numeric">   0</span><span class="z-string">     4M</span><span class="z-constant z-numeric">  0</span><span class="z-string"> disk</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">zd48</span><span class="z-string">                      230:48</span><span class="z-constant z-numeric">   0</span><span class="z-string">    80G</span><span class="z-constant z-numeric">  0</span><span class="z-string"> disk</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">├─zd48p1</span><span class="z-string">                  230:49</span><span class="z-constant z-numeric">   0</span><span class="z-string">     1M</span><span class="z-constant z-numeric">  0</span><span class="z-string"> part</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">└─zd48p2</span><span class="z-string">                  230:50</span><span class="z-constant z-numeric">   0</span><span class="z-string">    80G</span><span class="z-constant z-numeric">  0</span><span class="z-string"> part</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">zd64</span><span class="z-string">                      230:64</span><span class="z-constant z-numeric">   0</span><span class="z-string">    32G</span><span class="z-constant z-numeric">  0</span><span class="z-string"> disk</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">├─zd64p1</span><span class="z-string">                  230:65</span><span class="z-constant z-numeric">   0</span><span class="z-string">   512K</span><span class="z-constant z-numeric">  0</span><span class="z-string"> part</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">└─zd64p2</span><span class="z-string">                  230:66</span><span class="z-constant z-numeric">   0</span><span class="z-string">    32G</span><span class="z-constant z-numeric">  0</span><span class="z-string"> part</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">zd80</span><span class="z-string">                      230:80</span><span class="z-constant z-numeric">   0</span><span class="z-string">     1M</span><span class="z-constant z-numeric">  0</span><span class="z-string"> disk</span></span></code></pre>
<p>Our two current drives are <code>3QD0BG0J</code> and <code>3QD0BQ5G</code> as we can see in proxmox but we can also see that they have partitions and <code>sdb/3QD0BN6V</code> does not so thats our target drive. Now we can find the disk by id with <code>ls /dev/disk/by-id | grep 3QD0BN6V</code> which gives us:</p>
<blockquote>
<p>ls /dev/disk/by-id | grep 3QD0BN6V</p>
</blockquote>
<pre class="giallo z-code"><code data-lang="shellscript"><span class="giallo-l"><span class="z-entity z-name z-function">ata-ST3750640NS_3QD0BN6V</span></span></code></pre><figure class="2" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;9tkdGhqIYmxt.webp" alt="chick-fil-a macaroni and cheese with 2 nuggets and some ketchup"  />
    </div>
    
    <figcaption><p>My case situation is a bit of a mess and I’m using old 7200rpm server drives for pretty much everything; the dream is a 3 drive 2 TB each m.2 nvme ssd setup, maybe someday 🤷</p>
</figcaption>
    
</figure>
<p>We are going to go with the first id so no we move on to the zfs part. Running <code>zpool status vault-of-the-eldunari</code> we can get the status of the pool:</p>
<blockquote>
<p>zpool status vault-of-the-eldunari</p>
</blockquote>
<pre class="giallo z-code"><code data-lang="shellscript"><span class="giallo-l"><span class="z-entity z-name z-function">  pool:</span><span class="z-string"> vault-of-the-eldunari</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function"> state:</span><span class="z-string"> DEGRADED</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">status:</span><span class="z-string"> One or more devices could not be used because the label is missing or</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">        invalid.</span><span class="z-string">  Sufficient replicas exist for the pool to continue</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">        functioning</span><span class="z-string"> in a degraded state.</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">action:</span><span class="z-string z-punctuation z-definition z-string"> Replace the device using &#39;zpool replace&#39;.</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">   see:</span><span class="z-string"> https://openzfs.github.io/openzfs-docs/msg/ZFS-8000-4J</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">  scan:</span><span class="z-string"> resilvered 8.33G in 00:48:26 with</span><span class="z-constant z-numeric"> 0</span><span class="z-string"> errors on Thu Nov</span><span class="z-constant z-numeric"> 14</span><span class="z-string"> 18:38:03</span><span class="z-constant z-numeric"> 2024</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">config:</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-entity z-name z-function">        NAME</span><span class="z-string">                          STATE     READ WRITE CKSUM</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">        vault-of-the-eldunari</span><span class="z-string">         DEGRADED</span><span class="z-constant z-numeric">     0     0     0</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">          raidz1-0</span><span class="z-string">                    DEGRADED</span><span class="z-constant z-numeric">     0     0     0</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">            9201394420428878514</span><span class="z-string">       UNAVAIL</span><span class="z-constant z-numeric">      0     0     0</span><span class="z-string">  was /dev/disk/by-id/ata-ST3750640NS_3QD0BM29-part1</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">            ata-ST3750640NS_3QD0BQ5G</span><span class="z-string">  ONLINE</span><span class="z-constant z-numeric">       0     0     0</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">            ata-ST3750640NS_3QD0BG0J</span><span class="z-string">  ONLINE</span><span class="z-constant z-numeric">       0     0     0</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-entity z-name z-function">errors:</span><span class="z-string"> No known data errors</span></span></code></pre>
<p>We can add our new disk with <code>zpool replace vault-of-the-eldunari 9201394420428878514 ata-ST3750640NS_3QD0BN6V</code> but first we wipe the disk from proxmox under the disks tab on our proxmox node to make sure its all clean before we restore the pool after we do that we also initalize a new gpt table. Now we are ready to replace the disk. Running this command can take quite a while and it doesn’t output anything so sit tight. After waiting a few minutes proxmox reported that resilvering would take 1:49 minutes and it was 5% done already! I hope this helped at least one other person but I’m mainly writing this to remind myself how to do this when it inevitably happens again :)</p>
<figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;2fbRM2bxdHKc.webp" alt="the zpool reporting a downed disk"  />
    </div>
    
    <figcaption><p>It’s slow but faster then I expected for HDDs</p>
</figcaption>
    
</figure>
]]></content:encoded>
    </item>
    
    <item>
      <title>My life story in tech so far ig 🤷</title>
      <link>https://dunkirk.sh/blog/my-life-story-with-tech/</link>
      <guid isPermaLink="true">https://dunkirk.sh/blog/my-life-story-with-tech/</guid>
      <pubDate>Fri, 31 Jan 2025 00:00:00 +0000</pubDate>
      
      
      <dc:creator>Kieran Klukas</dc:creator>
      
      
        
      <category>yap fest</category>
        
      <category>biography</category>
        
      
      <description>
        
          &amp;lt;p&amp;gt;I was applying for a Cybersecurity college camp for this summer and realized this is honestly a pretty good summary of my life in tech so far (till i’m 16) and that I should probably make it a blog post soooo here it is!&amp;lt;&#x2F;p&amp;gt;
        
      </description>
      <content:encoded><![CDATA[<p>I was applying for a Cybersecurity college camp for this summer and realized this is honestly a pretty good summary of my life in tech so far (till i’m 16) and that I should probably make it a blog post soooo here it is!</p>
<span id="continue-reading"></span><h2 id="the-yap">The yap</h2>
<p>Hi! My name is Kieran, and I’ve been interested in / involved with cybersecurity and programming since I first started using a laptop at 10! I started out with a raspberry pi 3b+ which taught me how to use debian as well as the basics of creating and maintaining databases and web services. I moved on to an ubuntu laptop about a year latter and started using my raspberry pi as a home server to run small websites on our local lan. Soon I wanted to share them with others and expose them to the internet, so I learned how to use dns and port forwarding and then how to secure the server to prevent attacks with tools like fail2ban!</p>
<figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;_nNZW8nDrtsX.webp" alt="2 boxes of electronics sitting on a closet shelf"  />
    </div>
    
    <figcaption><p>I still have that same rpi today! It’s joined with all the random tech bits in two enormously heavy bins in my closet</p>
</figcaption>
    
</figure>
<p>Over the next 2 years, I systematically read every single book in the tech section of my local library and became interested in white-hat hacking. I taught myself how to use kali linux and metasploit with the help of many web searches and had quite a bit of fun rooting and then sideloading custom payloads onto our families set of kindle fires (I was eventually restricted to just playing with just one but I did make a home security system with all of them once). I figured out wireshark and started playing with wifi protocals but eventually reached the limit of what I could figure out on my own and took a quick detour of two years to learn blender and build my first computer.</p>
<p>I became interested in home labs and self hosting services around 14 and bought an old workstation off ebay which combined with my set of 3 rasberry pis and several old laptops (and one old pentium tower that I found on the side of the road) made quite a nice playground for deploying my own services. Half a year later I had to pick it all up and move up north which was quite the adventure; my services got completely messed up during the move, and it took my a week or so tinkering with everything to get it back to a stable state.</p>
<figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;hc-cdn.hel1.your-objectstorage.com&#x2F;s&#x2F;v3&#x2F;bf06e9d57d41dd75e328b8898cfe04ef2f30a3f3_0contributions-graph.gif" alt="gif of my github contributions graph 2021-2025"  />
    </div>
    
    <figcaption><p>2021-2022 is mainly just unity and hugo sites lol; I really started seriously using it and doing contributions to other projects 2023-2025. You can also see where I broke my wrist in January of 2025</p>
</figcaption>
    
</figure>
<p>After the move, I became quite interested in front end development and started making quite a few websites and various random coding projects. If you look on my GitHub contributions graph (<a rel="noopener external" target="_blank" href="https://github.com/taciturnaxolotl">github.com/taciturnaxolotl</a>, you can see it go from a lightly speckled grid in 2021 and 2022 to a much more solid commit streak in 2023. I only had one week when I didn’t code anything and that was the second week of the year :) Toward the end of that year I started learning about hardware design and made my first PCB! I also joined a wonderful community called hackclub where I met a ton of amazing teenagers who were also interested in tech just like me! I joined an FRC robotics team in January of the next year and had a blast designing, building, and programming a custom meter square, 150 lb, industrial robot to compete in that year’s game!</p>
<figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;nvbGwaMylcQ2.webp" alt="purple bubble logo"  />
    </div>
    
    <figcaption><p>I loved working on purple bubble 💖 i worked with some pretty incredible people and learned a ton. ik know yall are probably reading this when rss drops it so 🫶</p>
</figcaption>
    
</figure>
<p>During that same time I also started a 501(c)3 named Purple Bubble with friends that I had met through Hackclub focused on making a secure, cost-effective, and privacy preserving messaging protocol. We drafted a specification and poured many, many hours into planning and developing the protocol over the next year but eventual realized that the messaging protocol space is <em>incredibly</em> hard and that there were innate flaws in our protocol that would compromise the security of the app (We couldn’t find a good way to anonymize connections to a network of server’s while also providing zero metadata transfer of messages between servers; we had originally planned for the protocol to be zero trust federated, but this proved to be a challenge that, no matter how hard we kept thinking and talking about it, we couldn’t find a solution too). I learned a huge amount about organizing a group of people and running an organization through that experience and made some wonderful friends, so it wasn’t entirely in vain.</p>
<p>My latest project and biggest learning experience in both security and development has been building a time tracking server for coding called Hackatime. It is fully compatible with the popular wakatime.org, which allows it to leverage the hundreds of existing extensions for tracking time spent coding in almost every popular IDE and editor. I made this as a part of an event Hackclub ran called High Seas where they encouraged high school students to make cool projects by giving out awesome prizes for time spent coding (you had to “ship” your project where it would get voted on by the other four thousand teens participating and then via a custom ELO system convert your hours into “doubloons” that could be redeemed for prizes like framework laptops, soldering irons, McMaster Car credits, and many others. If you want to learn more about it, the website is <a rel="noopener external" target="_blank" href="https://highseas.hackclub.com">highseas.hackclub.com</a>). In order to track the time of the thousands of teenagers participating, I created this server which was handling thousands of users an hour and hundreds of requests a second. I learned how to scale the server and database and learned an incredible amount that only comes at scale. At one point I got an email that the database bill had increased so much over the previous month that we were going to hit both the <code>$1k</code> hard limit and then a <code>$4k</code> limit that I had placed on the monthly bill, expecting never to hit it. The team hosting the database (Cockroach DB) graciously offered to reduce our bill down to only <code>$500</code> which was incredible. There were many more instances where things broke, or where I discovered security issues that made me grow an insane amount in my knowledge of how to fix things and really pushed me out of my comfort zone. (If you want to take a look at the github repo it is at <a rel="noopener external" target="_blank" href="https://github.com/hackclub/hackatime">github.com/hackclub/hackatime</a> and the hosted version is at <a href="https://dunkirk.sh/blog/my-life-story-with-tech/https;//waka.hackclub.com">waka.hackclub.com</a> with a live hours counted tracker)</p>
<figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;QifqA0Ob4VJZ.webp" alt="the cockroach charges in hcb"  />
    </div>
    
    <figcaption><p>The price really sky rocketed as we started using it in prod 😂</p>
</figcaption>
    
</figure>
<p>I’m still trying to figure out what exactly I want to major in, and I’m pretty solidly split between Comp Sci with a cybersecurity focus and Computer/Electrical Engineering. I’m hoping that this camp can help make that decision a bit more clear and give me a better understanding of what getting a major in Cyber Security would be like!</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Hilton Tomfoolery</title>
      <link>https://dunkirk.sh/blog/hilton-tomfoolery/</link>
      <guid isPermaLink="true">https://dunkirk.sh/blog/hilton-tomfoolery/</guid>
      <pubDate>Sun, 13 Oct 2024 00:00:00 +0000</pubDate>
      
      
      <dc:creator>Kieran Klukas</dc:creator>
      
      
        
      <category>reverse engineering</category>
        
      <category>hilton</category>
        
      
      <description>
        
          &amp;lt;p&amp;gt;I’m at a Hilton at the time of writing this, and I’m decently bored. Currently, I’m downloading the latest version of RogueMaster (0.420.0) to my flipper, as it is currently crashing every time I open the NFC app. My dad tried out the app unlock feature in the Hilton app for the first time today, which, as most new tech things, made me quite curious how it worked and whether I could break it. Based on playing with it, there seems to be a proximity reading (over Bluetooth? Perhaps a BLE beacon?) to detect if you are by your door but for a period of time (~20 sec) after getting that signal it allows you to unlock the door from across the room which I’m guessing means that it controls the locks via a central server. The current plan is to install the root cert (of mitmproxy) on my iPhone and then try and intercept those API calls and see if we can manipulate them in any interesting ways. I’m also planning on live blogging this, which I’ve never tried before. (I also wrote this whole article in vim ^_^)&amp;lt;&#x2F;p&amp;gt;
        
      </description>
      <content:encoded><![CDATA[<p>I’m at a Hilton at the time of writing this, and I’m decently bored. Currently, I’m downloading the latest version of RogueMaster (0.420.0) to my flipper, as it is currently crashing every time I open the NFC app. My dad tried out the app unlock feature in the Hilton app for the first time today, which, as most new tech things, made me quite curious how it worked and whether I could break it. Based on playing with it, there seems to be a proximity reading (over Bluetooth? Perhaps a BLE beacon?) to detect if you are by your door but for a period of time (~20 sec) after getting that signal it allows you to unlock the door from across the room which I’m guessing means that it controls the locks via a central server. The current plan is to install the root cert (of mitmproxy) on my iPhone and then try and intercept those API calls and see if we can manipulate them in any interesting ways. I’m also planning on live blogging this, which I’ve never tried before. (I also wrote this whole article in vim ^_^)</p>
<span id="continue-reading"></span><h2 id="connecting-to-mitmproxy">Connecting to Mitmproxy</h2>
<p>I’m connecting over WireGuard, so I fired up mitmproxy with <code>mitmweb --mode wireguard</code> on my laptop. Connecting via WireGuard theoretically is pretty simple; all I need to do is to scan a qr code and connect. Unfortunately, the hotel Wi-Fi seems to be oddly segmented, and I can’t access the WireGuard server or ping my laptop from my phone. I’m going to try firing up a hot spot on my dad’s phone and see if that allows me to talk to my phone.</p>
<figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;iYaDaVPYV1Lc.webp" alt="screenshot of the root certificate install process"  />
    </div>
    
    <figcaption><p>You have to dig through several menus to trust it</p>
</figcaption>
    
</figure>
<p>I messed with getting my laptop to connect to my dad’s phone, but it kept refusing for some reason. My next idea is to ngrok the WireGuard tunnel, which ended up failing because ngrok doesn’t support UDP. Finally, after an embarrassingly long time, I realized that I could simply use <code>ngrok tcp 8080</code> and the HTTP proxy server built into mitmproxy instead. After installing the root certificate and trusting it in the iPhone settings, we were good to go!</p>
<h2 id="digging-around-in-the-hilton-honors-app">Digging around in the Hilton Honors app</h2>
<p>First I had to download the app, which required disabling the proxy as iOS seems to ignore certificate trust settings for the app store. Enrollment happened via the <code>https://m.hilton.io/graphql/customer?operationName=createGuest&amp;type=enroll</code> endpoint and was as follows:</p>
<blockquote>
<p>POST</p>
</blockquote>
<pre class="giallo z-code"><code data-lang="json"><span class="giallo-l"><span class="z-punctuation">{</span></span>
<span class="giallo-l"><span class="z-punctuation">  &quot;</span><span class="z-support z-type z-property-name z-json">query</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;...&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">  &quot;</span><span class="z-support z-type z-property-name z-json">variables</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">    &quot;</span><span class="z-support z-type z-property-name z-json">input</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">      &quot;</span><span class="z-support z-type z-property-name z-json">email</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">        &quot;</span><span class="z-support z-type z-property-name z-json">emailAddress</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;xxxx-xxxx-xxxx@duck.com&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">      },</span></span>
<span class="giallo-l"><span class="z-punctuation">      &quot;</span><span class="z-support z-type z-property-name z-json">preferredLanguage</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;EN&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">      &quot;</span><span class="z-support z-type z-property-name z-json">enrollSourceCode</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;IOSEW&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">      &quot;</span><span class="z-support z-type z-property-name z-json">phone</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">        &quot;</span><span class="z-support z-type z-property-name z-json">phoneNumber</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;xxxxxxxxxx&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">        &quot;</span><span class="z-support z-type z-property-name z-json">phoneType</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;home&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">      },</span></span>
<span class="giallo-l"><span class="z-punctuation">      &quot;</span><span class="z-support z-type z-property-name z-json">subscriptions</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">        &quot;</span><span class="z-support z-type z-property-name z-json">hhonorsSubscriptions</span><span class="z-punctuation">&quot;: [],</span></span>
<span class="giallo-l"><span class="z-punctuation">        &quot;</span><span class="z-support z-type z-property-name z-json">optOuts</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">          &quot;</span><span class="z-support z-type z-property-name z-json">survey</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-language z-json"> false</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">          &quot;</span><span class="z-support z-type z-property-name z-json">marketing</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-language z-json"> false</span></span>
<span class="giallo-l"><span class="z-punctuation">        }</span></span>
<span class="giallo-l"><span class="z-punctuation">      },</span></span>
<span class="giallo-l"><span class="z-punctuation">      &quot;</span><span class="z-support z-type z-property-name z-json">name</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">        &quot;</span><span class="z-support z-type z-property-name z-json">firstName</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Kieran&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">        &quot;</span><span class="z-support z-type z-property-name z-json">lastName</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Klukas&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">      },</span></span>
<span class="giallo-l"><span class="z-punctuation">      &quot;</span><span class="z-support z-type z-property-name z-json">address</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">        &quot;</span><span class="z-support z-type z-property-name z-json">city</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Washington&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">        &quot;</span><span class="z-support z-type z-property-name z-json">addressType</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;home&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">        &quot;</span><span class="z-support z-type z-property-name z-json">addressLine2</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-language z-json"> null</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">        &quot;</span><span class="z-support z-type z-property-name z-json">postalCode</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;20003&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">        &quot;</span><span class="z-support z-type z-property-name z-json">state</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;DC&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">        &quot;</span><span class="z-support z-type z-property-name z-json">addressLine1</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;1600 Pennsylvania Ave SE Apartments&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">        &quot;</span><span class="z-support z-type z-property-name z-json">country</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;US&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">      },</span></span>
<span class="giallo-l"><span class="z-punctuation">      &quot;</span><span class="z-support z-type z-property-name z-json">privacyRequested</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-language z-json"> false</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">      &quot;</span><span class="z-support z-type z-property-name z-json">password</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;xxxxxxxxxxxxxxxxxxxxxxxxxxx&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">    },</span></span>
<span class="giallo-l"><span class="z-punctuation">    &quot;</span><span class="z-support z-type z-property-name z-json">language</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;en_US&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">  },</span></span>
<span class="giallo-l"><span class="z-punctuation">  &quot;</span><span class="z-support z-type z-property-name z-json">operationName</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;createGuest&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">}</span></span></code></pre><pre class="giallo z-code"><code data-lang="shellscript"><span class="giallo-l"><span class="z-entity z-name z-function">---</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">mutation</span><span class="z-string"> createGuest</span><span class="z-punctuation">(</span><span class="z-punctuation z-definition z-variable z-source">$input: EnrollInput</span><span class="z-keyword z-operator">!</span><span class="z-entity z-name z-function">,</span><span class="z-punctuation z-definition z-variable z-source"> $language</span><span class="z-string">: String!</span><span class="z-punctuation">)</span><span class="z-string"> {</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">    createGuest(language:</span><span class="z-punctuation z-definition z-variable z-source"> $language</span><span class="z-string">, input:</span><span class="z-punctuation z-definition z-variable z-source"> $input) </span><span class="z-punctuation">{</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">        data</span><span class="z-string"> {</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">            guestId</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">            hhonorsNumber</span></span>
<span class="giallo-l"><span class="z-punctuation">        }</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">        error</span><span class="z-string"> {</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">            code</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">            context</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">            message</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">            notifications</span><span class="z-string"> {</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">                code</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">                fields</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">                message</span></span>
<span class="giallo-l"><span class="z-source">            }</span></span>
<span class="giallo-l"><span class="z-source">        }</span></span>
<span class="giallo-l"><span class="z-source">    }</span></span>
<span class="giallo-l"><span class="z-source">}</span></span></code></pre><br/>
<blockquote>
<p>response</p>
</blockquote>
<pre class="giallo z-code"><code data-lang="json"><span class="giallo-l"><span class="z-punctuation">{</span></span>
<span class="giallo-l"><span class="z-punctuation">    &quot;</span><span class="z-support z-type z-property-name z-json">data</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">        &quot;</span><span class="z-support z-type z-property-name z-json">createGuest</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">            &quot;</span><span class="z-support z-type z-property-name z-json">data</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">                &quot;</span><span class="z-support z-type z-property-name z-json">guestId</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-numeric"> 1726240000</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                &quot;</span><span class="z-support z-type z-property-name z-json">hhonorsNumber</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;225782xxxx&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">            },</span></span>
<span class="giallo-l"><span class="z-punctuation">            &quot;</span><span class="z-support z-type z-property-name z-json">error</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-language z-json"> null</span></span>
<span class="giallo-l"><span class="z-punctuation">        }</span></span>
<span class="giallo-l"><span class="z-punctuation">    },</span></span>
<span class="giallo-l"><span class="z-punctuation">    &quot;</span><span class="z-support z-type z-property-name z-json">extensions</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">        &quot;</span><span class="z-support z-type z-property-name z-json">logSearch</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;mdc.client_message_id=51da35305bde9b71e7faa2993ebc2a619e50c598-iap71t195vk6jur6eyd83hy9g21w4bzam&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">    }</span></span>
<span class="giallo-l"><span class="z-punctuation">}</span></span></code></pre><br/>
<blockquote>
<p>headers</p>
</blockquote>
<pre class="giallo z-code"><code data-lang="json"><span class="giallo-l"><span class="z-punctuation">{</span></span>
<span class="giallo-l"><span class="z-punctuation">    &quot;</span><span class="z-support z-type z-property-name z-json">access_token</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;DX.eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00iLCJwaWQiOiJtb2JpbGUiLCJraWQiOiJWTktOaU9WLTZ4c2cyOXQ2dEJUMmR5Nnl1TEJPUG8tb2FycDFpZzZnVWdZIn0.Yfxkp8Jnrwttmrp_QcMTp6HwW2yBzEIROsAjheNVd3LgIWBQVfw5UDstAd7qA3MKZffoKSf7SbkopJhWIr-vreOYQ2BZEf2a9DYm4p-tD9jXEMsuVY9offYqjRzyeWwcjLyWq9ubnUBpQuCHMNUO1025BZkNV3NYG3_LZfJlNc77aMawrfS52zi5I5hSL0zAs3K7kxYuReJEC-RzASt-coPHEmdkmDp1TLqujqe8Opy6z4DC8xFDFkO09J-tN6RwolJ6jtssr0vnFyCv9zw_lbAQppB0jbWZqxiEmNk_krC4laOsChe3bUJc8ECeKltvqgVnSAKAhz-zBfy9EbFeew.Wki3N1rclvnjMfA4.dtWVnkpMJZixW86Q9hiiBY30_Oa1NHPLM_SuuAjtTrY-QZilp5tgu7COJtiVYI51_j6nIOHdX2oI0EoiHaPhzC4YizFxNbZsUfpR0W6wPVWj3LpdTr23GMhoOga5UTFCbaehb5XCsWr9PLfnLc2tSGyi4wZOSGrSidQUCDQ6UssUTxt6vvlp5y623EbvkMEi-ok6IXqUnYgsztcz_i14GKRHdRmJZFACJj3X0zQLarN4b19KEwvqIXfIrpPWpr1f74ozamM6CUEQhqoF61cucKCxKf0hU7kAyMduo4l3OfEkghQUfrlfA1D7eoInyPcOb8a3_LjQhGXwh1XVoElXUriuP7yEOfyksv59_pKCWajzJuyWdEl164OZXAFmMkdQ89flDO3_nRZUliMXapnWkU3WDBGD_gQ49sYbxlAh95l0HiJeKZwf2g4DTlEb6ccauRAbUzD2Fopoe2ldMXL-wBkVg2Grx8SfaCnOCiyfGq2HloJMf-8YRz-tWQoTXFEM30KdJCWY70sUTY9LeWVQrz4dnpZlRk29KyNi20YsdQRK3y9_ZFL0qs4IJwhddtrhzQVKz6oaxDPgQxy2vK1DErers-8-oJ3WgDho9l9D7Z7U9C1spjf1IIBG5hvdtCiExqh78fFsizcvkG9oeHB09Z1oGU3jL_cUFKrrUp9ZXnOKlwU1BjFPrOjVVZi97-rVN3IjvlRjJCBfFCf2CxlbZcib_CWSiD0vtFsloClkmSho2ynnbLQG341SibvaO4TKygttS-NsluDjBtpuJydlNjDAXO6ZvWRiFWcHDrDqiBeo897yUM40kHYFXBpjhbiIDcCnAJu6GDozbacnGsEGOJlauASm3t8TFn1lPd_kQgd3Uy2fDtTCKxxSaXA4RvHwUbBgYWU4SMA7UPYn_RygkxUZ0UL4ZHfN1-bDpkQ16DLm0Q.hh53MImM9BA7Ujib61RUOg&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">    &quot;</span><span class="z-support z-type z-property-name z-json">exp</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-numeric"> 1728879203</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">    &quot;</span><span class="z-support z-type z-property-name z-json">expires_in</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-numeric"> 3600</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">    &quot;</span><span class="z-support z-type z-property-name z-json">iat</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-numeric"> 1728875603</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">    &quot;</span><span class="z-support z-type z-property-name z-json">scope</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;am_application_scope default&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">    &quot;</span><span class="z-support z-type z-property-name z-json">token_type</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Bearer&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">}</span></span></code></pre>
<p>At this point I went to bed as it was about 23:30, but I set my alarm for 5:30 (if you know me I never get up before 8:00, so this is rare) and actually managed to wake-up on time. It’s always quite curious how excitement and a new place can cause you to wake-up earlier. Unfortunately, while I was sleeping my laptop died which caused me to lose the rest of the signup data. I’m going to invite myself to get the room key and see what API requests that triggers, and then try actually unlocking the door.</p>
<h1 id="invitation">Invitation</h1>
<blockquote>
<p>Hi Kieran,<br />
I’m sharing my Digital Key so you can use your phone as a room key.<br />
If you already have the Hilton Honors app make sure you are on the latest version and click this https://hiltonhonors3.hilton.com/rs/hilton-honors-mobile-app?shareId=b4d6140d311e4c4c935dd653ca00af65 to accept the Digital Key.<br />
If you don’t have the Hilton Honors app, download it using the link above and return to this message to accept within 8 hours to get your key.<br />
See you soon,<br />
xxxx xxxx</p>
</blockquote>
<p>I shared the key which asked for a name and then opened the iOS share sheet and I choose to send by text (it sent the above message). I went back to my phone, clicked the link and low and behold we got a hit! <code>https://hms.hiltonapi.com/hms/v1/digitalkey/invitation/accept</code>:</p>
<blockquote>
<p>POST</p>
</blockquote>
<pre class="giallo z-code"><code data-lang="json"><span class="giallo-l"><span class="z-punctuation">{</span></span>
<span class="giallo-l"><span class="z-punctuation">    &quot;</span><span class="z-support z-type z-property-name z-json">shareId</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;b4d6140d311e4c4c935dd653ca00af65&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">}</span></span></code></pre><br/>
<blockquote>
<p>response</p>
</blockquote>
<pre class="giallo z-code"><code data-lang="json"><span class="giallo-l"><span class="z-punctuation">{</span></span>
<span class="giallo-l"><span class="z-punctuation">    &quot;</span><span class="z-support z-type z-property-name z-json">arrivalDateTime</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;2024-10-13T15:00-04:00&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">    &quot;</span><span class="z-support z-type z-property-name z-json">confirmationNumber</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;5448xxxx&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">    &quot;</span><span class="z-support z-type z-property-name z-json">ctyhocn</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;GCYPAHX&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">    &quot;</span><span class="z-support z-type z-property-name z-json">departureDateTime</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;2024-10-14T11:00-04:00&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">    &quot;</span><span class="z-support z-type z-property-name z-json">hotelName</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Hampton Inn &amp; Suites Grove City&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">    &quot;</span><span class="z-support z-type z-property-name z-json">stayId</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;296088xxxx&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">}</span></span></code></pre>
<p>Another interesting request was to <code>https://m.hilton.io/graphql/customer?operationName=hotel_brand&amp;type=hotelDetails_GCYPAHX</code></p>
<blockquote>
<p>POST</p>
</blockquote>
<pre class="giallo z-code"><code data-lang="json"><span class="giallo-l"><span class="z-punctuation">{</span></span>
<span class="giallo-l"><span class="z-punctuation">  &quot;</span><span class="z-support z-type z-property-name z-json">variables</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">    &quot;</span><span class="z-support z-type z-property-name z-json">ctyhocn</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;GCYPAHX&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">    &quot;</span><span class="z-support z-type z-property-name z-json">language</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;en-US&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">  },</span></span>
<span class="giallo-l"><span class="z-punctuation">  &quot;</span><span class="z-support z-type z-property-name z-json">operationName</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;hotel_brand&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">  &quot;</span><span class="z-support z-type z-property-name z-json">query</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;...&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">}</span></span></code></pre><pre class="giallo z-code"><code data-lang="shellscript"><span class="giallo-l"><span class="z-entity z-name z-function">---</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">query</span><span class="z-string"> hotel_brand</span><span class="z-punctuation">(</span><span class="z-punctuation z-definition z-variable z-source">$language: String</span><span class="z-keyword z-operator">!</span><span class="z-entity z-name z-function">,</span><span class="z-punctuation z-definition z-variable z-source"> $ctyhocn</span><span class="z-string">: String!</span><span class="z-punctuation">)</span><span class="z-string"> {</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">  hotel(language:</span><span class="z-punctuation z-definition z-variable z-source"> $language</span><span class="z-string">, ctyhocn:</span><span class="z-punctuation z-definition z-variable z-source"> $ctyhocn) </span><span class="z-punctuation">{</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">    address</span><span class="z-string"> {</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">      addressFmt</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">      addressLine1</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">      addressLine2</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">      city</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">      country</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">      countryName</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">      postalCode</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">      state</span></span>
<span class="giallo-l"><span class="z-punctuation">    }</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">    alerts</span><span class="z-string"> {</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">      description</span></span>
<span class="giallo-l"><span class="z-support z-function z-builtin">      type</span></span>
<span class="giallo-l"><span class="z-source">    }</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">    amenities</span><span class="z-string"> {</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">      id</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">      name</span></span>
<span class="giallo-l"><span class="z-source">    }</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">    attributes</span><span class="z-string"> {</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">      numberOfRestaurants</span></span>
<span class="giallo-l"><span class="z-source">    }</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">    brand</span><span class="z-string"> {</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">      isPartnerBrand</span></span>
<span class="giallo-l"><span class="z-source">    }</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">    brandCode</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">    campus</span><span class="z-string"> {</span></span>
<span class="giallo-l"><span class="z-support z-function z-builtin">      type</span></span>
<span class="giallo-l"><span class="z-source">    }</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">    chainCode</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">    checkin</span><span class="z-string"> {</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">      checkinTimeFmt</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">      checkinTime</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">      checkoutTimeFmt</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">      checkoutTime</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">      digitalKey</span></span>
<span class="giallo-l"><span class="z-source">    }</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">    config</span><span class="z-string"> {</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">      checkout</span><span class="z-string"> {</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">        allowDCO</span></span>
<span class="giallo-l"><span class="z-source">      }</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">      connectedRoom</span><span class="z-string"> {</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">        crEnabled</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">        crFullyEnabled</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">        emsEnabled</span></span>
<span class="giallo-l"><span class="z-source">      }</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">      messaging</span><span class="z-string"> {</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">        kipsuEnabled</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">        messagingTileEnabled</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">        gatewayRoutingEnabled</span></span>
<span class="giallo-l"><span class="z-source">      }</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">      adjoiningRooms</span><span class="z-string"> {</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">        active</span></span>
<span class="giallo-l"><span class="z-source">      }</span></span>
<span class="giallo-l"><span class="z-source">    }</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">    coordinate</span><span class="z-string"> {</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">      latitude</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">      longitude</span></span>
<span class="giallo-l"><span class="z-source">    }</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">    crsData</span><span class="z-string"> {</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">      adultAge</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">      ageBasedPricing</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">      acceptedCreditCards</span></span>
<span class="giallo-l"><span class="z-source">    }</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">    ctyhocn</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">    currencyCode</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">    display</span><span class="z-string"> {</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">      treatments</span></span>
<span class="giallo-l"><span class="z-source">    }</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">    gmtHours</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">    internetAddress</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">    name</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">    phoneNumber</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">    policyOptions</span><span class="z-string"> {</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">      label</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">      value</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">      options</span><span class="z-string"> {</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">        label</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">        value</span></span>
<span class="giallo-l"><span class="z-source">      }</span></span>
<span class="giallo-l"><span class="z-source">    }</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">    facilityOverview</span><span class="z-string"> {</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">      shortDesc</span></span>
<span class="giallo-l"><span class="z-source">    }</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">    images</span><span class="z-string"> {</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">      master</span><span class="z-string"> {</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">        altText</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">        url(height:</span><span class="z-string"> 430, width:</span><span class="z-constant z-numeric"> 612</span><span class="z-source">)</span></span>
<span class="giallo-l"><span class="z-source">      }</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">      gallery</span><span class="z-string"> {</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">        image(variant:</span><span class="z-string"> searchPropertyImageThumbnail</span><span class="z-source">) </span><span class="z-punctuation">{</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">          altText</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">          url(height:</span><span class="z-string"> 430, width:</span><span class="z-constant z-numeric"> 612</span><span class="z-source">)</span></span>
<span class="giallo-l"><span class="z-punctuation">        }</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">        masterImage</span></span>
<span class="giallo-l"><span class="z-source">      }</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">      carousel</span><span class="z-string"> {</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">        altText</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">        url(height:</span><span class="z-string"> 430, width:</span><span class="z-constant z-numeric"> 612</span><span class="z-source">)</span></span>
<span class="giallo-l"><span class="z-source">      }</span></span>
<span class="giallo-l"><span class="z-source">    }</span></span>
<span class="giallo-l"><span class="z-source">  }</span></span>
<span class="giallo-l"><span class="z-source">}</span></span></code></pre><br/>
<blockquote>
<p>response</p>
</blockquote>
<pre class="giallo z-code"><code data-lang="json"><span class="giallo-l"><span class="z-punctuation">{</span></span>
<span class="giallo-l"><span class="z-punctuation">    &quot;</span><span class="z-support z-type z-property-name z-json">data</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">        &quot;</span><span class="z-support z-type z-property-name z-json">hotel</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">            &quot;</span><span class="z-support z-type z-property-name z-json">address</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">                &quot;</span><span class="z-support z-type z-property-name z-json">addressFmt</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;4 Holiday Blvd., Mercer, Pennsylvania, 16137, USA&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                &quot;</span><span class="z-support z-type z-property-name z-json">addressLine1</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;4 Holiday Blvd.&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                &quot;</span><span class="z-support z-type z-property-name z-json">addressLine2</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-language z-json"> null</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                &quot;</span><span class="z-support z-type z-property-name z-json">city</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Mercer&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                &quot;</span><span class="z-support z-type z-property-name z-json">country</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;US&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                &quot;</span><span class="z-support z-type z-property-name z-json">countryName</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;USA&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                &quot;</span><span class="z-support z-type z-property-name z-json">postalCode</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;16137&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                &quot;</span><span class="z-support z-type z-property-name z-json">state</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;PA&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">            },</span></span>
<span class="giallo-l"><span class="z-punctuation">            &quot;</span><span class="z-support z-type z-property-name z-json">alerts</span><span class="z-punctuation">&quot;: [</span></span>
<span class="giallo-l"><span class="z-punctuation">                {</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">description</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;The hotel will be undergoing exterior renovations September 03, 2024 - December 31, 2024. The interior and guestrooms will be unaffected. Thank you for your patience and understanding.&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">type</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;alert&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                }</span></span>
<span class="giallo-l"><span class="z-punctuation">            ],</span></span>
<span class="giallo-l"><span class="z-punctuation">            &quot;</span><span class="z-support z-type z-property-name z-json">amenities</span><span class="z-punctuation">&quot;: [</span></span>
<span class="giallo-l"><span class="z-punctuation">                {</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">id</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;adjoiningRooms&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">name</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Connecting Rooms&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                },</span></span>
<span class="giallo-l"><span class="z-punctuation">                {</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">id</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;freeBreakfast&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">name</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Free hot breakfast&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                },</span></span>
<span class="giallo-l"><span class="z-punctuation">                {</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">id</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;freeParking&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">name</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Free parking&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                },</span></span>
<span class="giallo-l"><span class="z-punctuation">                {</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">id</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;freeWifi&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">name</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Free WiFi&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                },</span></span>
<span class="giallo-l"><span class="z-punctuation">                {</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">id</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;nonSmoking&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">name</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Non-smoking rooms&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                },</span></span>
<span class="giallo-l"><span class="z-punctuation">                {</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">id</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;digitalKey&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">name</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Digital Key&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                },</span></span>
<span class="giallo-l"><span class="z-punctuation">                {</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">id</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;evCharging&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">name</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;EV charging&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                },</span></span>
<span class="giallo-l"><span class="z-punctuation">                {</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">id</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;indoorPool&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">name</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Indoor pool&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                },</span></span>
<span class="giallo-l"><span class="z-punctuation">                {</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">id</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;fitnessCenter&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">name</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Fitness center&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                },</span></span>
<span class="giallo-l"><span class="z-punctuation">                {</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">id</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;petsAllowed&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">name</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Pet-friendly rooms&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                },</span></span>
<span class="giallo-l"><span class="z-punctuation">                {</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">id</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;meetingRooms&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">name</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Meeting rooms&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                }</span></span>
<span class="giallo-l"><span class="z-punctuation">            ],</span></span>
<span class="giallo-l"><span class="z-punctuation">            &quot;</span><span class="z-support z-type z-property-name z-json">attributes</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">                &quot;</span><span class="z-support z-type z-property-name z-json">numberOfRestaurants</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-numeric"> 0</span></span>
<span class="giallo-l"><span class="z-punctuation">            },</span></span>
<span class="giallo-l"><span class="z-punctuation">            &quot;</span><span class="z-support z-type z-property-name z-json">brand</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">                &quot;</span><span class="z-support z-type z-property-name z-json">isPartnerBrand</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-language z-json"> false</span></span>
<span class="giallo-l"><span class="z-punctuation">            },</span></span>
<span class="giallo-l"><span class="z-punctuation">            &quot;</span><span class="z-support z-type z-property-name z-json">brandCode</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;HP&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">            &quot;</span><span class="z-support z-type z-property-name z-json">campus</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">                &quot;</span><span class="z-support z-type z-property-name z-json">type</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;enhanced&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">            },</span></span>
<span class="giallo-l"><span class="z-punctuation">            &quot;</span><span class="z-support z-type z-property-name z-json">chainCode</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;HH&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">            &quot;</span><span class="z-support z-type z-property-name z-json">checkin</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">                &quot;</span><span class="z-support z-type z-property-name z-json">checkinTime</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;15:00&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                &quot;</span><span class="z-support z-type z-property-name z-json">checkinTimeFmt</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;3:00 PM&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                &quot;</span><span class="z-support z-type z-property-name z-json">checkoutTime</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;11:00&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                &quot;</span><span class="z-support z-type z-property-name z-json">checkoutTimeFmt</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;11:00 AM&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                &quot;</span><span class="z-support z-type z-property-name z-json">digitalKey</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-language z-json"> true</span></span>
<span class="giallo-l"><span class="z-punctuation">            },</span></span>
<span class="giallo-l"><span class="z-punctuation">            &quot;</span><span class="z-support z-type z-property-name z-json">config</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">                &quot;</span><span class="z-support z-type z-property-name z-json">adjoiningRooms</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">active</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-language z-json"> true</span></span>
<span class="giallo-l"><span class="z-punctuation">                },</span></span>
<span class="giallo-l"><span class="z-punctuation">                &quot;</span><span class="z-support z-type z-property-name z-json">checkout</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">allowDCO</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-language z-json"> true</span></span>
<span class="giallo-l"><span class="z-punctuation">                },</span></span>
<span class="giallo-l"><span class="z-punctuation">                &quot;</span><span class="z-support z-type z-property-name z-json">connectedRoom</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-language z-json"> null</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                &quot;</span><span class="z-support z-type z-property-name z-json">messaging</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">gatewayRoutingEnabled</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-language z-json"> true</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">kipsuEnabled</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-language z-json"> true</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">messagingTileEnabled</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-language z-json"> true</span></span>
<span class="giallo-l"><span class="z-punctuation">                }</span></span>
<span class="giallo-l"><span class="z-punctuation">            },</span></span>
<span class="giallo-l"><span class="z-punctuation">            &quot;</span><span class="z-support z-type z-property-name z-json">coordinate</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">                &quot;</span><span class="z-support z-type z-property-name z-json">latitude</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-numeric"> 41.142354</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                &quot;</span><span class="z-support z-type z-property-name z-json">longitude</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-numeric"> -80.164956</span></span>
<span class="giallo-l"><span class="z-punctuation">            },</span></span>
<span class="giallo-l"><span class="z-punctuation">            &quot;</span><span class="z-support z-type z-property-name z-json">crsData</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">                &quot;</span><span class="z-support z-type z-property-name z-json">acceptedCreditCards</span><span class="z-punctuation">&quot;: [</span></span>
<span class="giallo-l"><span class="z-punctuation z-definition z-string z-string">                    &quot;CU&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation z-definition z-string z-string">                    &quot;VI&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation z-definition z-string z-string">                    &quot;MC&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation z-definition z-string z-string">                    &quot;AX&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation z-definition z-string z-string">                    &quot;DS&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation z-definition z-string z-string">                    &quot;DC&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation z-definition z-string z-string">                    &quot;CB&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                ],</span></span>
<span class="giallo-l"><span class="z-punctuation">                &quot;</span><span class="z-support z-type z-property-name z-json">adultAge</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-language z-json"> null</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                &quot;</span><span class="z-support z-type z-property-name z-json">ageBasedPricing</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-language z-json"> null</span></span>
<span class="giallo-l"><span class="z-punctuation">            },</span></span>
<span class="giallo-l"><span class="z-punctuation">            &quot;</span><span class="z-support z-type z-property-name z-json">ctyhocn</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;GCYPAHX&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">            &quot;</span><span class="z-support z-type z-property-name z-json">currencyCode</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;USD&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">            &quot;</span><span class="z-support z-type z-property-name z-json">display</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">                &quot;</span><span class="z-support z-type z-property-name z-json">treatments</span><span class="z-punctuation">&quot;: []</span></span>
<span class="giallo-l"><span class="z-punctuation">            },</span></span>
<span class="giallo-l"><span class="z-punctuation">            &quot;</span><span class="z-support z-type z-property-name z-json">facilityOverview</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">                &quot;</span><span class="z-support z-type z-property-name z-json">shortDesc</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;We&#39;re off I-79, 10 minutes from Grove City. Grove City Premium Outlets and Wendell August Forge, America&#39;s oldest and largest working forge, are less than a mile away. Both Grove City College and Slippery Rock University are within 20 minutes of us. Enjoy free hot breakfast, free WiFi, and our indoor pool and hot tub.&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">            },</span></span>
<span class="giallo-l"><span class="z-punctuation">            &quot;</span><span class="z-support z-type z-property-name z-json">gmtHours</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-numeric"> -4</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">            &quot;</span><span class="z-support z-type z-property-name z-json">images</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">                &quot;</span><span class="z-support z-type z-property-name z-json">carousel</span><span class="z-punctuation">&quot;: [</span></span>
<span class="giallo-l"><span class="z-punctuation">                    {</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">altText</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Outdoor Patio&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">url</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;https://www.hilton.com/im/en/GCYPAHX/2888809/gcypahx-patio.jpg?impolicy=resize&amp;rh=430&amp;rw=612&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                    },</span></span>
<span class="giallo-l"><span class="z-punctuation">                    {</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">altText</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Double Queen&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">url</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;https://www.hilton.com/im/en/GCYPAHX/4248848/hampton-grove-city-standard-queen-1-preview.jpg?impolicy=resize&amp;rh=430&amp;rw=612&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                    },</span></span>
<span class="giallo-l"><span class="z-punctuation">                    {</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">altText</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;King Standard&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">url</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;https://www.hilton.com/im/en/GCYPAHX/4248016/hampton-grove-city-king-standard-1.jpg?impolicy=resize&amp;rh=430&amp;rw=612&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                    },</span></span>
<span class="giallo-l"><span class="z-punctuation">                    {</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">altText</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Lobby and Dining Area&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">url</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;https://www.hilton.com/im/en/GCYPAHX/2887422/lobby-1.jpg?impolicy=resize&amp;rh=430&amp;rw=612&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                    },</span></span>
<span class="giallo-l"><span class="z-punctuation">                    {</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">altText</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Lobby&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">url</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;https://www.hilton.com/im/en/GCYPAHX/2887224/lobby-2.jpg?impolicy=resize&amp;rh=430&amp;rw=612&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                    },</span></span>
<span class="giallo-l"><span class="z-punctuation">                    {</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">altText</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Breakfast Buffet&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">url</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;https://www.hilton.com/im/en/GCYPAHX/2880542/breakfast-1.jpg?impolicy=resize&amp;rh=430&amp;rw=612&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                    },</span></span>
<span class="giallo-l"><span class="z-punctuation">                    {</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">altText</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Indoor Pool&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">url</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;https://www.hilton.com/im/en/GCYPAHX/4258295/hampton-new-pics-2008-035.jpg?impolicy=resize&amp;rh=430&amp;rw=612&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                    },</span></span>
<span class="giallo-l"><span class="z-punctuation">                    {</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">altText</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Fitness Center&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">url</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;https://www.hilton.com/im/en/GCYPAHX/2883297/fitness.jpg?impolicy=resize&amp;rh=430&amp;rw=612&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                    },</span></span>
<span class="giallo-l"><span class="z-punctuation">                    {</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">altText</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Boardroom&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">url</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;https://www.hilton.com/im/en/GCYPAHX/2879933/board-room.jpg?impolicy=resize&amp;rh=430&amp;rw=612&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                    },</span></span>
<span class="giallo-l"><span class="z-punctuation">                    {</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">altText</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Meeting Room&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">url</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;https://www.hilton.com/im/en/GCYPAHX/2888194/meeting.jpg?impolicy=resize&amp;rh=430&amp;rw=612&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                    },</span></span>
<span class="giallo-l"><span class="z-punctuation">                    {</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">altText</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Hotel Exterior at Night&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">url</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;https://www.hilton.com/im/en/GCYPAHX/4251655/exterior-night.jpg?impolicy=resize&amp;rh=430&amp;rw=612&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                    },</span></span>
<span class="giallo-l"><span class="z-punctuation">                    {</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">altText</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Business Center&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">url</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;https://www.hilton.com/im/en/GCYPAHX/2881016/business-center.jpg?impolicy=resize&amp;rh=430&amp;rw=612&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                    }</span></span>
<span class="giallo-l"><span class="z-punctuation">                ],</span></span>
<span class="giallo-l"><span class="z-punctuation">                &quot;</span><span class="z-support z-type z-property-name z-json">gallery</span><span class="z-punctuation">&quot;: [</span></span>
<span class="giallo-l"><span class="z-punctuation">                    {</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">image</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">altText</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Outdoor Patio&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">url</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;https://www.hilton.com/im/en/GCYPAHX/2888809/gcypahx-patio.jpg?impolicy=resize&amp;rh=430&amp;rw=612&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                        },</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">masterImage</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-language z-json"> true</span></span>
<span class="giallo-l"><span class="z-punctuation">                    },</span></span>
<span class="giallo-l"><span class="z-punctuation">                    {</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">image</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">altText</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Double Queen&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">url</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;https://www.hilton.com/im/en/GCYPAHX/4248848/hampton-grove-city-standard-queen-1-preview.jpg?impolicy=resize&amp;rh=430&amp;rw=612&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                        },</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">masterImage</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-language z-json"> false</span></span>
<span class="giallo-l"><span class="z-punctuation">                    },</span></span>
<span class="giallo-l"><span class="z-punctuation">                    {</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">image</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">altText</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;King Standard&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">url</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;https://www.hilton.com/im/en/GCYPAHX/4248016/hampton-grove-city-king-standard-1.jpg?impolicy=resize&amp;rh=430&amp;rw=612&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                        },</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">masterImage</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-language z-json"> false</span></span>
<span class="giallo-l"><span class="z-punctuation">                    },</span></span>
<span class="giallo-l"><span class="z-punctuation">                    {</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">image</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">altText</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Lobby and Dining Area&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">url</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;https://www.hilton.com/im/en/GCYPAHX/2887422/lobby-1.jpg?impolicy=resize&amp;rh=430&amp;rw=612&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                        },</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">masterImage</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-language z-json"> false</span></span>
<span class="giallo-l"><span class="z-punctuation">                    },</span></span>
<span class="giallo-l"><span class="z-punctuation">                    {</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">image</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">altText</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Lobby&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">url</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;https://www.hilton.com/im/en/GCYPAHX/2887224/lobby-2.jpg?impolicy=resize&amp;rh=430&amp;rw=612&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                        },</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">masterImage</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-language z-json"> false</span></span>
<span class="giallo-l"><span class="z-punctuation">                    },</span></span>
<span class="giallo-l"><span class="z-punctuation">                    {</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">image</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">altText</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Breakfast Buffet&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">url</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;https://www.hilton.com/im/en/GCYPAHX/2880542/breakfast-1.jpg?impolicy=resize&amp;rh=430&amp;rw=612&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                        },</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">masterImage</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-language z-json"> false</span></span>
<span class="giallo-l"><span class="z-punctuation">                    },</span></span>
<span class="giallo-l"><span class="z-punctuation">                    {</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">image</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">altText</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Indoor Pool&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">url</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;https://www.hilton.com/im/en/GCYPAHX/4258295/hampton-new-pics-2008-035.jpg?impolicy=resize&amp;rh=430&amp;rw=612&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                        },</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">masterImage</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-language z-json"> false</span></span>
<span class="giallo-l"><span class="z-punctuation">                    },</span></span>
<span class="giallo-l"><span class="z-punctuation">                    {</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">image</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">altText</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Fitness Center&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">url</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;https://www.hilton.com/im/en/GCYPAHX/2883297/fitness.jpg?impolicy=resize&amp;rh=430&amp;rw=612&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                        },</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">masterImage</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-language z-json"> false</span></span>
<span class="giallo-l"><span class="z-punctuation">                    },</span></span>
<span class="giallo-l"><span class="z-punctuation">                    {</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">image</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">altText</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Boardroom&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">url</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;https://www.hilton.com/im/en/GCYPAHX/2879933/board-room.jpg?impolicy=resize&amp;rh=430&amp;rw=612&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                        },</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">masterImage</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-language z-json"> false</span></span>
<span class="giallo-l"><span class="z-punctuation">                    },</span></span>
<span class="giallo-l"><span class="z-punctuation">                    {</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">image</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">altText</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Meeting Room&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">url</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;https://www.hilton.com/im/en/GCYPAHX/2888194/meeting.jpg?impolicy=resize&amp;rh=430&amp;rw=612&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                        },</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">masterImage</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-language z-json"> false</span></span>
<span class="giallo-l"><span class="z-punctuation">                    },</span></span>
<span class="giallo-l"><span class="z-punctuation">                    {</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">image</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">altText</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Hotel Exterior at Night&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">url</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;https://www.hilton.com/im/en/GCYPAHX/4251655/exterior-night.jpg?impolicy=resize&amp;rh=430&amp;rw=612&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                        },</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">masterImage</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-language z-json"> false</span></span>
<span class="giallo-l"><span class="z-punctuation">                    },</span></span>
<span class="giallo-l"><span class="z-punctuation">                    {</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">image</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">altText</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Business Center&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">url</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;https://www.hilton.com/im/en/GCYPAHX/2881016/business-center.jpg?impolicy=resize&amp;rh=430&amp;rw=612&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                        },</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">masterImage</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-language z-json"> false</span></span>
<span class="giallo-l"><span class="z-punctuation">                    },</span></span>
<span class="giallo-l"><span class="z-punctuation">                    {</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">image</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">altText</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Indoor Pool / Whirlpool&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">url</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;https://www.hilton.com/im/en/GCYPAHX/2885342/pool.jpg?impolicy=resize&amp;rh=430&amp;rw=612&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                        },</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">masterImage</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-language z-json"> false</span></span>
<span class="giallo-l"><span class="z-punctuation">                    },</span></span>
<span class="giallo-l"><span class="z-punctuation">                    {</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">image</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">altText</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Double Queen Studio&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">url</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;https://www.hilton.com/im/en/GCYPAHX/4250170/hampton-new-pics-2008-020.jpg?impolicy=resize&amp;rh=430&amp;rw=612&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                        },</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">masterImage</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-language z-json"> false</span></span>
<span class="giallo-l"><span class="z-punctuation">                    },</span></span>
<span class="giallo-l"><span class="z-punctuation">                    {</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">image</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">altText</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Whirlpool Suite&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">url</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;https://www.hilton.com/im/en/GCYPAHX/4251204/whirlpool-king-suite-one-2-2-.jpg?impolicy=resize&amp;rh=430&amp;rw=612&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                        },</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">masterImage</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-language z-json"> false</span></span>
<span class="giallo-l"><span class="z-punctuation">                    },</span></span>
<span class="giallo-l"><span class="z-punctuation">                    {</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">image</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">altText</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Breakfast Area&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">url</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;https://www.hilton.com/im/en/GCYPAHX/2880342/breakfast-2.tif?impolicy=resize&amp;rh=430&amp;rw=612&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                        },</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">masterImage</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-language z-json"> false</span></span>
<span class="giallo-l"><span class="z-punctuation">                    },</span></span>
<span class="giallo-l"><span class="z-punctuation">                    {</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">image</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">altText</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Suite Shop&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">url</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;https://www.hilton.com/im/en/GCYPAHX/2890823/gift-shop.jpg?impolicy=resize&amp;rh=430&amp;rw=612&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                        },</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">masterImage</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-language z-json"> false</span></span>
<span class="giallo-l"><span class="z-punctuation">                    },</span></span>
<span class="giallo-l"><span class="z-punctuation">                    {</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">image</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">altText</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Hotel Exterior&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">url</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;https://www.hilton.com/im/en/GCYPAHX/390670/gcypahx-hampton-exterior-night-1.jpg?impolicy=resize&amp;rh=430&amp;rw=612&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                        },</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">masterImage</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-language z-json"> false</span></span>
<span class="giallo-l"><span class="z-punctuation">                    },</span></span>
<span class="giallo-l"><span class="z-punctuation">                    {</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">image</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">altText</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Hotel Exterior&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">url</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;https://www.hilton.com/im/en/GCYPAHX/4255766/hampton-new-pics-2008-012.jpg?impolicy=resize&amp;rh=430&amp;rw=612&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                        },</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">masterImage</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-language z-json"> false</span></span>
<span class="giallo-l"><span class="z-punctuation">                    },</span></span>
<span class="giallo-l"><span class="z-punctuation">                    {</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">image</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">altText</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Double Queen Suite&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">url</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;https://www.hilton.com/im/en/GCYPAHX/4251117/double-queen-standard-2-.jpg?impolicy=resize&amp;rh=430&amp;rw=612&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                        },</span></span>
<span class="giallo-l"><span class="z-punctuation">                        &quot;</span><span class="z-support z-type z-property-name z-json">masterImage</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-language z-json"> false</span></span>
<span class="giallo-l"><span class="z-punctuation">                    }</span></span>
<span class="giallo-l"><span class="z-punctuation">                ],</span></span>
<span class="giallo-l"><span class="z-punctuation">                &quot;</span><span class="z-support z-type z-property-name z-json">master</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">altText</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Outdoor Patio&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">url</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;https://www.hilton.com/im/en/GCYPAHX/2888809/gcypahx-patio.jpg?impolicy=resize&amp;rh=430&amp;rw=612&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                }</span></span>
<span class="giallo-l"><span class="z-punctuation">            },</span></span>
<span class="giallo-l"><span class="z-punctuation">            &quot;</span><span class="z-support z-type z-property-name z-json">internetAddress</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;https://www.hilton.com/en/hotels/gcypahx-hampton-suites-grove-city/&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">            &quot;</span><span class="z-support z-type z-property-name z-json">name</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Hampton Inn &amp; Suites Grove City&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">            &quot;</span><span class="z-support z-type z-property-name z-json">phoneNumber</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;+1 724-748-5744&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">            &quot;</span><span class="z-support z-type z-property-name z-json">policyOptions</span><span class="z-punctuation">&quot;: [</span></span>
<span class="giallo-l"><span class="z-punctuation">                {</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">label</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Cancellation&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">options</span><span class="z-punctuation">&quot;: [</span></span>
<span class="giallo-l"><span class="z-punctuation">                        {</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">label</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Description&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">value</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Cancellation policies may vary depending on the rate or dates of your reservation. Please refer to your reservation confirmation to verify your cancellation policy. If you need further assistance, call the hotel directly or contact &lt;a href=https://help.hilton.com/s/&gt;customer service&lt;/a&gt;. Alternatively, you can &lt;a href=https://www.hilton.com/en/book/reservation/find/&gt;cancel your reservation online&lt;/a&gt;.&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                        }</span></span>
<span class="giallo-l"><span class="z-punctuation">                    ],</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">value</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-language z-json"> null</span></span>
<span class="giallo-l"><span class="z-punctuation">                },</span></span>
<span class="giallo-l"><span class="z-punctuation">                {</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">label</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Check-in/Check-out&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">options</span><span class="z-punctuation">&quot;: [</span></span>
<span class="giallo-l"><span class="z-punctuation">                        {</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">label</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Checkin Time&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">value</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;3:00 PM&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                        },</span></span>
<span class="giallo-l"><span class="z-punctuation">                        {</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">label</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Checkout Time&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">value</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;11:00 AM&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                        },</span></span>
<span class="giallo-l"><span class="z-punctuation">                        {</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">label</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Early Departure Fee&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">value</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-language z-json"> null</span></span>
<span class="giallo-l"><span class="z-punctuation">                        },</span></span>
<span class="giallo-l"><span class="z-punctuation">                        {</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">label</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Late Checkout Fee&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">value</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-language z-json"> null</span></span>
<span class="giallo-l"><span class="z-punctuation">                        },</span></span>
<span class="giallo-l"><span class="z-punctuation">                        {</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">label</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Minimum Age To Register&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">value</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;21&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                        }</span></span>
<span class="giallo-l"><span class="z-punctuation">                    ],</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">value</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-language z-json"> null</span></span>
<span class="giallo-l"><span class="z-punctuation">                },</span></span>
<span class="giallo-l"><span class="z-punctuation">                {</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">label</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Wi-Fi&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">options</span><span class="z-punctuation">&quot;: [</span></span>
<span class="giallo-l"><span class="z-punctuation">                        {</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">label</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Description&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">value</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Standard In-Room and Lobby Wi-Fi - All guests get free standard Wi-Fi in-room and in the lobby.&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                        }</span></span>
<span class="giallo-l"><span class="z-punctuation">                    ],</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">value</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-language z-json"> null</span></span>
<span class="giallo-l"><span class="z-punctuation">                },</span></span>
<span class="giallo-l"><span class="z-punctuation">                {</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">label</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Parking&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">options</span><span class="z-punctuation">&quot;: [</span></span>
<span class="giallo-l"><span class="z-punctuation">                        {</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">label</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Covered&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">value</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Not Available&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                        },</span></span>
<span class="giallo-l"><span class="z-punctuation">                        {</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">label</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;In Out Privileges&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">value</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Not Available&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                        },</span></span>
<span class="giallo-l"><span class="z-punctuation">                        {</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">label</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Other Parking Information&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">value</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Parking Lot&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                        },</span></span>
<span class="giallo-l"><span class="z-punctuation">                        {</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">label</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Secured&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">value</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Not Available&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                        },</span></span>
<span class="giallo-l"><span class="z-punctuation">                        {</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">label</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Self Parking&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">value</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;$0.00 Complimentary&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                        },</span></span>
<span class="giallo-l"><span class="z-punctuation">                        {</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">label</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Valet Parking&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">value</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Not Available&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                        }</span></span>
<span class="giallo-l"><span class="z-punctuation">                    ],</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">value</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-language z-json"> null</span></span>
<span class="giallo-l"><span class="z-punctuation">                },</span></span>
<span class="giallo-l"><span class="z-punctuation">                {</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">label</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Payment&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">options</span><span class="z-punctuation">&quot;: [</span></span>
<span class="giallo-l"><span class="z-punctuation">                        {</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">label</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Hotel Currency&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">value</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;US Dollar&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                        },</span></span>
<span class="giallo-l"><span class="z-punctuation">                        {</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">label</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Accepted Payment Options&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">value</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;American Express, Carte Blanche, China Union Pay, Diner&#39;s Club, Discover, MasterCard, Visa&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                        }</span></span>
<span class="giallo-l"><span class="z-punctuation">                    ],</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">value</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-language z-json"> null</span></span>
<span class="giallo-l"><span class="z-punctuation">                },</span></span>
<span class="giallo-l"><span class="z-punctuation">                {</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">label</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Pets&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">options</span><span class="z-punctuation">&quot;: [</span></span>
<span class="giallo-l"><span class="z-punctuation">                        {</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">label</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Deposit&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">value</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;$50.00 Non-Refundable&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                        },</span></span>
<span class="giallo-l"><span class="z-punctuation">                        {</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">label</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Kennel Message&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">value</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-language z-json"> null</span></span>
<span class="giallo-l"><span class="z-punctuation">                        },</span></span>
<span class="giallo-l"><span class="z-punctuation">                        {</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">label</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Maximum Size&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">value</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-language z-json"> null</span></span>
<span class="giallo-l"><span class="z-punctuation">                        },</span></span>
<span class="giallo-l"><span class="z-punctuation">                        {</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">label</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Maximum Weight&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">value</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-language z-json"> null</span></span>
<span class="giallo-l"><span class="z-punctuation">                        },</span></span>
<span class="giallo-l"><span class="z-punctuation">                        {</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">label</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Other Pet Services&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">value</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;$50(1-4n),$75(5+n) 2petsMax,dog/cat only&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                        },</span></span>
<span class="giallo-l"><span class="z-punctuation">                        {</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">label</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Pets Allowed&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">value</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Yes&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                        },</span></span>
<span class="giallo-l"><span class="z-punctuation">                        {</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">label</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Service Animals Allowed&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">value</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Yes&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                        }</span></span>
<span class="giallo-l"><span class="z-punctuation">                    ],</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">value</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-language z-json"> null</span></span>
<span class="giallo-l"><span class="z-punctuation">                },</span></span>
<span class="giallo-l"><span class="z-punctuation">                {</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">label</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Smoking&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">options</span><span class="z-punctuation">&quot;: [</span></span>
<span class="giallo-l"><span class="z-punctuation">                        {</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">label</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Indicator&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">value</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Non-Smoking&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">                        },</span></span>
<span class="giallo-l"><span class="z-punctuation">                        {</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">label</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Description&quot;</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-punctuation">                            &quot;</span><span class="z-support z-type z-property-name z-json">value</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-language z-json"> null</span></span>
<span class="giallo-l"><span class="z-punctuation">                        }</span></span>
<span class="giallo-l"><span class="z-punctuation">                    ],</span></span>
<span class="giallo-l"><span class="z-punctuation">                    &quot;</span><span class="z-support z-type z-property-name z-json">value</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-language z-json"> null</span></span>
<span class="giallo-l"><span class="z-punctuation">                }</span></span>
<span class="giallo-l"><span class="z-punctuation">            ]</span></span>
<span class="giallo-l"><span class="z-punctuation">        }</span></span>
<span class="giallo-l"><span class="z-punctuation">    },</span></span>
<span class="giallo-l"><span class="z-punctuation">    &quot;</span><span class="z-support z-type z-property-name z-json">extensions</span><span class="z-punctuation">&quot;: {</span></span>
<span class="giallo-l"><span class="z-punctuation">        &quot;</span><span class="z-support z-type z-property-name z-json">logSearch</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string"> &quot;mdc.client_message_id=51da35305bde9b71e7faa2993ebc2a619e50c598-uauhdwbaydoqbvy3uvuzai20c4takt22&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">    }</span></span>
<span class="giallo-l"><span class="z-punctuation">}</span></span></code></pre>
<p>It appears that Hilton relies very heavily on GraphQL, which is interesting. I would be interested in playing with those APIs more. For now, though, onto unlocking stuff!</p>
<h2 id="locks">Locks</h2>
<figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;JfDm-stHm3hH.webp" alt="screenshot of the hotel digital key"  />
    </div>
    
    <figcaption><p>What it looks like in the app</p>
</figcaption>
    
</figure>
<p>When using the unlock button, it made a request to this URL: <code>https://smetric.hilton.com/b/ss/hiltonglobalprod/10/IOSN030200030900/s65425920</code> with a payload of a URL encoded form.</p>
<blockquote>
<p>POST</p>
</blockquote>
<pre class="giallo z-code"><code data-lang="yaml"><span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">ndh</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-constant z-numeric">              1</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">cid.</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source">             </span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">card_no.</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source">         </span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">as</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-constant z-numeric">               0</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">id</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-constant z-numeric">               2257829743</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">.card_no</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source">         </span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">hhid.</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source">            </span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">as</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-constant z-numeric">               1</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">id</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-constant z-numeric">               2257829743</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">.hhid</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source">            </span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">.cid</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source">             </span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">aamb</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-string">             j8Odv6LonN4r3an7LhD3WZrU1bUpAkFkkiY1ncBR96t2PTI</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">aamlh</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-constant z-numeric">            7</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">c.</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source">               </span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">a.</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source">               </span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">AppID</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-string">            HHonors 2024.10.1 (1)</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">CarrierName</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-string">      --</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">DeviceName</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-string">       iPhone14,4</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">OSVersion</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-string">        iOS 18.1</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">Resolution</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-string">       1125x2436</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">RunMode</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-string">          Application</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">TimeSinceLaunch</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-constant z-numeric">  24560</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">action</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-string">           digital key:key:unlock_btn</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">.a</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source">               </span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">hm.</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source">              </span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">app.</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source">             </span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">name</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-string">             HHonors iOS Mobile</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">.app</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source">             </span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">digitalkeyflag</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-constant z-language z-boolean">   Yes</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">event.</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source">           </span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">element.</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source">         </span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">click</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-string">            digital key:key:unlock_btn</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">.element</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source">         </span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">key.</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source">             </span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">unlock.</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source">          </span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">initiate</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-constant z-numeric">         1</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">.unlock</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source">          </span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">.key</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source">             </span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">.event</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source">           </span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">flag.</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source">            </span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">appsettings</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-string">      N|N|NA|N|Y|Y|Y|Y|Y|NA|UsingApp|N|</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">orientation</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-string">      L</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">stay.</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source">            </span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">level.</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source">           </span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">status</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-string">           In-Stay</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">.level</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source">           </span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">.stay</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source">            </span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">timeCaptureAEP</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source">   2024-10-14T09:59:44.676Z</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">timeCaptureEpoch</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-constant z-numeric"> 1728899984</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">timeCaptureISO</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-string">   2024-10-14T05:59:44-0400</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">.flag</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source">            </span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">hotel.</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source">           </span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">brand</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-string">            HP</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">propertycode</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-string">     GCYPAHX</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">.hotel</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source">           </span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">key.</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source">             </span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">gnr</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-constant z-numeric">              79125065</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">locktype</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-string">         guest</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">lsn</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-constant z-numeric">              92044507</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">shared.</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source">          </span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">flag</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-constant z-language z-boolean">             Yes</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">.shared</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source">          </span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">.key</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source">             </span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">page.</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source">            </span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">name</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source">             </span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">previous</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-string">         App:EN:Honors:Digital Key:Key</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">.page</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source">            </span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">purchase.</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source">        </span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">booking.</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source">         </span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">dates</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-string">            10132024:10142024:1</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">.booking</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source">         </span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">bookingid</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-constant z-numeric">        54486330</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">.purchase</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source">        </span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">site.</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source">            </span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">type</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-string">             PA</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">.site</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source">            </span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">user.</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source">            </span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">aam.</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source">             </span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">segments</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-string">         15218869,26458327,19493122,21537957,22516131,17952857,23583601,17952894,19484989,21539153,22889861,21539313,26458383,21881915,15217574</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">.aam</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source">             </span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">language</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-string">         en</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">login.</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source">           </span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">status</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-string">           Logged-in</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">.login</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source">           </span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">memberId</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-constant z-numeric">         2257829743</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">stayid</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-constant z-numeric">           2960880196</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">tierpoints</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-string">       Member</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">.user</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source">            </span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">.hm</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source">              </span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">.c</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-source">               </span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">ce</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-string">               UTF-8</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">cp</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-string">               foreground</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">d.ptfm</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-string">           ios</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">mid</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-constant z-numeric">              61621496110939688115558742623055817571</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">pageName</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-string">         HHonors 2024.10.1 (1)</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">pe</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-string">               lnk_o</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">pev2</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-string">             AMACTION:digital key:key:unlock_btn</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">products</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-string">         ;GCYPAHX;;;;</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">t</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-string">                00/00/0000 00:00:00 0 240</span></span>
<span class="giallo-l"><span class="z-entity z-name z-tag z-yaml">ts</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-constant z-numeric">               1728899984</span></span></code></pre><br/>
<blockquote>
<p>response</p>
</blockquote>
<pre class="giallo z-code"><code data-lang="json"><span class="giallo-l"><span class="z-punctuation"> {</span></span>
<span class="giallo-l"><span class="z-punctuation">  &quot;</span><span class="z-support z-type z-property-name z-json">stuff</span><span class="z-punctuation">&quot;:[ {</span></span>
<span class="giallo-l"><span class="z-punctuation">    &quot;</span><span class="z-support z-type z-property-name z-json">cn</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string">&quot;TMS&quot;</span><span class="z-punctuation">,&quot;</span><span class="z-support z-type z-property-name z-json">cv</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string">&quot;web=17836315,Web-app=15217574,Web-app=17952857,Web-app=17952894,web-app=19493122,web-app=19484989,web-app=21539153,web-app=21539313,web-app=21881915,web-app=22516131,web-app=22889861,web-app=23583601,web-app=15218869,web-app=26458327,web-app=26458383,web-app=21537957&quot;</span><span class="z-punctuation">,&quot;</span><span class="z-support z-type z-property-name z-json">ttl</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-numeric">30</span><span class="z-punctuation">,&quot;</span><span class="z-support z-type z-property-name z-json">dmn</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string">&quot;&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">  }</span></span>
<span class="giallo-l"><span class="z-punctuation">  , {</span></span>
<span class="giallo-l"><span class="z-punctuation">    &quot;</span><span class="z-support z-type z-property-name z-json">cn</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string">&quot;fltk&quot;</span><span class="z-punctuation">,&quot;</span><span class="z-support z-type z-property-name z-json">cv</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string">&quot;segID=15218869&quot;</span><span class="z-punctuation">,&quot;</span><span class="z-support z-type z-property-name z-json">ttl</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-numeric">30</span><span class="z-punctuation">,&quot;</span><span class="z-support z-type z-property-name z-json">dmn</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string">&quot;&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">  }</span></span>
<span class="giallo-l"><span class="z-punctuation">  ],&quot;</span><span class="z-support z-type z-property-name z-json">uuid</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string">&quot;61645808922583835885560882535048239660&quot;</span><span class="z-punctuation">,&quot;</span><span class="z-support z-type z-property-name z-json">dcs_region</span><span class="z-punctuation">&quot;:</span><span class="z-constant z-numeric">7</span><span class="z-punctuation">,&quot;</span><span class="z-support z-type z-property-name z-json">tid</span><span class="z-punctuation">&quot;:</span><span class="z-punctuation z-definition z-string z-string">&quot;RufgJCfxTjg=&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">}</span></span></code></pre>
<p>About a second afterward, I get a second request to <code>https://smetric.hilton.com/b/ss/hiltonglobalprod/10/IOSN030200030900/s88785229</code> with similar form data. Diff shown below.</p>
<blockquote>
<p>POST.diff</p>
</blockquote>
<pre class="giallo z-code"><code data-lang="diff"><span class="giallo-l"><span class="z-source">23c23</span></span>
<span class="giallo-l"><span class="z-punctuation">&lt;</span><span class="z-markup z-deleted z-diff"> action:           digital key:key:unlock_btn</span></span>
<span class="giallo-l"><span class="z-punctuation">---</span></span>
<span class="giallo-l"><span class="z-punctuation">&gt;</span><span class="z-markup z-inserted z-diff"> action:           digital key:key:unlock:unlock_success</span></span>
<span class="giallo-l"><span class="z-source">31,33d30</span></span>
<span class="giallo-l"><span class="z-punctuation">&lt;</span><span class="z-markup z-deleted z-diff"> element.:</span></span>
<span class="giallo-l"><span class="z-punctuation">&lt;</span><span class="z-markup z-deleted z-diff"> click:            digital key:key:unlock_btn</span></span>
<span class="giallo-l"><span class="z-punctuation">&lt;</span><span class="z-markup z-deleted z-diff"> .element:</span></span>
<span class="giallo-l"><span class="z-source">36c33</span></span>
<span class="giallo-l"><span class="z-punctuation">&lt;</span><span class="z-markup z-deleted z-diff"> initiate:         1</span></span>
<span class="giallo-l"><span class="z-punctuation">---</span></span>
<span class="giallo-l"><span class="z-punctuation">&gt;</span><span class="z-markup z-inserted z-diff"> success:          1</span></span>
<span class="giallo-l"><span class="z-source">48,50c45,47</span></span>
<span class="giallo-l"><span class="z-punctuation">&lt;</span><span class="z-markup z-deleted z-diff"> timeCaptureAEP:   2024-10-14T09:59:44.676Z</span></span>
<span class="giallo-l"><span class="z-punctuation">&lt;</span><span class="z-markup z-deleted z-diff"> timeCaptureEpoch: 1728899984</span></span>
<span class="giallo-l"><span class="z-punctuation">&lt;</span><span class="z-markup z-deleted z-diff"> timeCaptureISO:   2024-10-14T05:59:44-0400</span></span>
<span class="giallo-l"><span class="z-punctuation">---</span></span>
<span class="giallo-l"><span class="z-punctuation">&gt;</span><span class="z-markup z-inserted z-diff"> timeCaptureAEP:   2024-10-14T09:59:45.537Z</span></span>
<span class="giallo-l"><span class="z-punctuation">&gt;</span><span class="z-markup z-inserted z-diff"> timeCaptureEpoch: 1728899985</span></span>
<span class="giallo-l"><span class="z-punctuation">&gt;</span><span class="z-markup z-inserted z-diff"> timeCaptureISO:   2024-10-14T05:59:45-0400</span></span>
<span class="giallo-l"><span class="z-source">79c76</span></span>
<span class="giallo-l"><span class="z-punctuation">&lt;</span><span class="z-markup z-deleted z-diff"> segments:         15218869,26458327,19493122,21537957,22516131,17952857,23583601,17952894,19484989,21539153,22889861,21539313,26458383,21881915,15217574</span></span>
<span class="giallo-l"><span class="z-punctuation">---</span></span>
<span class="giallo-l"><span class="z-punctuation">&gt;</span><span class="z-markup z-inserted z-diff"> segments:         21537957,22889861,23583601,15218869,17952857,21881915,21539313,22516131,19484989,26458383,19493122,17952894,15217574,21539153,26458327</span></span>
<span class="giallo-l"><span class="z-source">97c94</span></span>
<span class="giallo-l"><span class="z-punctuation">&lt;</span><span class="z-markup z-deleted z-diff"> pev2:             AMACTION:digital key:key:unlock_btn</span></span>
<span class="giallo-l"><span class="z-punctuation">---</span></span>
<span class="giallo-l"><span class="z-punctuation">&gt;</span><span class="z-markup z-inserted z-diff"> pev2:             AMACTION:digital key:key:unlock:unlock_success</span></span>
<span class="giallo-l"><span class="z-source">100c97,99</span></span>
<span class="giallo-l"><span class="z-punctuation">&lt;</span><span class="z-markup z-deleted z-diff"> ts:               1728899984</span></span>
<span class="giallo-l"><span class="z-punctuation">---</span></span>
<span class="giallo-l"><span class="z-punctuation">&gt;</span><span class="z-markup z-inserted z-diff"> ts:               1728899985</span></span>
<span class="giallo-l"><span class="z-punctuation">&gt;</span></span>
<span class="giallo-l"><span class="z-punctuation">&gt;</span><span class="z-markup z-inserted z-diff"> *:8080mitmproxy 10.4.2 </span></span></code></pre><br/>
<blockquote>
<p>response.diff</p>
</blockquote>
<pre class="giallo z-code"><code data-lang="diff"><span class="giallo-l"><span class="z-punctuation">&lt;</span><span class="z-markup z-deleted z-diff">   ],&quot;uuid&quot;:&quot;61645808922583835885560882535048239660&quot;,&quot;dcs_region&quot;:7,&quot;tid&quot;:&quot;RufgJCfxTjg=&quot;</span></span>
<span class="giallo-l"><span class="z-punctuation">---</span></span>
<span class="giallo-l"><span class="z-punctuation">&gt;</span><span class="z-markup z-inserted z-diff">   ],&quot;uuid&quot;:&quot;61645808922583835885560882535048239660&quot;,&quot;dcs_region&quot;:7,&quot;tid&quot;:&quot;69dMPcWjQD4=&quot;</span></span></code></pre>
<p>Replaying either of the requests does nothing except give a new <code>tid</code> value but doesn’t unlock the door. The <code>sxxxxxxx</code> part of the request URL also changes on every new request and doesn’t seem to match any discernible pattern. The <code>IOSN030200030900</code> part never changes, however. My guess is that that part is a hotel reference ID. From doing some ducking around online, I couldn’t find any references to the <code>smetric.hilton.com</code> domain, but it was blocked by uBlock Origin as part of the <a rel="noopener external" target="_blank" href="https://easylist.to/#easyprivacy">EasyPrivacy</a> block list. The app also seems to issue requests to this URL.</p>
<h2 id="wrap-up">Wrap up</h2>
<figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;hc-cdn.hel1.your-objectstorage.com&#x2F;s&#x2F;v3&#x2F;4e9bfb28c266eb29cea1568cedd3573be2ba1f97_1bluetooth-scan.png" alt="screenshot of bluetooth scan"  />
    </div>
    
    <figcaption><p>The bluetooth scan of (what i believe is) the lift</p>
</figcaption>
    
</figure>
<p>I tried running a Bluetooth scan to see if I could find the locks, but nothing popped out as being a likely culprit. I did however find an interesting set of 3 Bluetooth devices named “clearsky smart fleet” which upon research seems to be scissor lifts / construction equipment made by a company called <a rel="noopener external" target="_blank" href="https://smartfleet.jlg.com/">JLG</a> which is quite interesting. That would make sense, however, as I saw several scissor lifts outside the hotel on my way in.</p>
<figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;hc-cdn.hel1.your-objectstorage.com&#x2F;s&#x2F;v3&#x2F;993ad810e42289ad3aaefa4093ede271a4ee1d12_0img_2781.jpg" alt="image of JLG lift"  />
    </div>
    
    <figcaption><p>The same (probably) JLG lift in the wild!</p>
</figcaption>
    
</figure>
<p>By the time I’m writing this it’s 6:41, and I need to eat breakfast, so I’ll probably finish this post in the car this afternoon. Overall this was a fascinating experiment and while I sadly did fail at unlocking doors from my laptop I do feel more confident with reverse engineering app requests now! The next step would probably be to grab the app bundle and try to decompile it looking for the URLs we saw, but I don’t have a mac on me, and I’ve never done that before. Next post?</p>
<p>Taking inspiration from the <a rel="noopener external" target="_blank" href="https://solar.lowtechmagazine.com/">LOW←TECH MAGAZINE</a> I will be taking any questions / comments about this article via email and then posting them here to my site! If you have a question or comment, feel free to email me at <a href="mailto://me@dunkirk.sh">me@dunkirk.sh</a>. Now to go eat breakfast :)</p>
<figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;CNEMtC8HNWRs.webp" alt="image of my hotel breakfast"  />
    </div>
    
    <figcaption><p>A delicious waffle, mildy warm bacon, and under seasoned potatoes.</p>
</figcaption>
    
</figure>
]]></content:encoded>
    </item>
    
    <item>
      <title>The *Mega* test case</title>
      <link>https://dunkirk.sh/blog/mega/</link>
      <guid isPermaLink="true">https://dunkirk.sh/blog/mega/</guid>
      <pubDate>Fri, 11 Oct 2024 00:00:00 +0000</pubDate>
      
      
      <dc:creator>Kieran Klukas</dc:creator>
      
      
        
      <category>meta</category>
        
      
      <description>
        
          &amp;lt;p&amp;gt;This post is for me to just test out all the features and styling of the blog, and to
make sure that if I change the CSS or anything I don’t break any of it! This is also a
sort of light style guide for blog posts in general.&amp;lt;&#x2F;p&amp;gt;
        
      </description>
      <content:encoded><![CDATA[<p>This post is for me to just test out all the features and styling of the blog, and to
make sure that if I change the CSS or anything I don’t break any of it! This is also a
sort of light style guide for blog posts in general.</p>
<span id="continue-reading"></span><h2 id="section-headers">Section Headers</h2>
<p>Sections headers (prefixed with <code>##</code> in markdown) are the main content separators for posts, and
can be <a href="https://dunkirk.sh/blog/mega/#section-headers">linked to</a> directly. To link to them, the header’s text needs to be
<em>kebab-cased</em>, so the above would be <code>#section-headers</code>.</p>
<p>Not quite a section header, the <code>&lt;!-- more --&gt;</code> tag is used to indicate where a post should be split for rss purposes. This should generally be right after the first paragraph.</p>
<h3 id="table-of-contents">Table of Contents</h3>
<p>Section and sub-headers can be used to generate a table of contents at the start of the page. To
enable this feature for a post, add the following to the page’s frontmatter:</p>
<blockquote>
<p>toml</p>
</blockquote>
<pre class="giallo z-code"><code data-lang="toml"><span class="giallo-l"><span class="z-punctuation">[</span><span class="z-source">extra</span><span class="z-punctuation">]</span></span>
<span class="giallo-l"><span class="z-source">has_toc</span><span class="z-punctuation z-separator z-key-value"> =</span><span class="z-constant z-language z-boolean"> true</span></span></code></pre>
<p>The table of contents will only ever be generated for <code>##</code> and <code>###</code> headers. I don’t particularly love the look of it and tend to write shorter posts so I hardly use it.</p>
<h2 id="embedding-code">Embedding Code</h2>
<p>I tend to do this alot so this is an important bit of the blog. All code blocks with a code type are progressively enhanced with a copy button.</p>
<h3 id="syntax-highlighting">Syntax Highlighting</h3>
<p>If you want syntax coloring, you put the name of the programming language immediately after the ticks.
So writing this:</p>
<pre class="giallo z-code"><code data-lang="markdown"><span class="giallo-l"><span class="z-punctuation z-definition">```</span><span class="z-fenced_code z-block z-language">rust</span></span>
<span class="giallo-l"><span class="z-keyword">fn</span><span class="z-entity z-name z-function"> main</span><span class="z-punctuation">() {</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function z-macro z-rust">    println!</span><span class="z-punctuation">(</span><span class="z-punctuation z-definition z-string z-string">&quot;Hello, world!&quot;</span><span class="z-punctuation">);</span></span>
<span class="giallo-l"><span class="z-punctuation">}</span></span>
<span class="giallo-l"><span class="z-punctuation z-definition">```</span></span></code></pre>
<p>Will produce this:</p>
<pre class="giallo z-code"><code data-lang="rust"><span class="giallo-l"><span class="z-keyword">fn</span><span class="z-entity z-name z-function"> main</span><span class="z-punctuation">() {</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function z-macro z-rust">    println!</span><span class="z-punctuation">(</span><span class="z-punctuation z-definition z-string z-string">&quot;Hello, world!&quot;</span><span class="z-punctuation">);</span></span>
<span class="giallo-l"><span class="z-punctuation">}</span></span></code></pre><h3 id="code-block-title">Code Block Title</h3>
<p>Sometimes it can help to give a header to a code block to signal what it represents. To do this, you put
a single-line block quote immediately before the code block. So by prepending the following code with
<code>&gt; src/index.ts</code>, I can produce this:</p>
<blockquote>
<p>src/index.ts</p>
</blockquote>
<pre class="giallo z-code"><code data-lang="typescript"><span class="giallo-l"><span class="z-source">Bun</span><span class="z-punctuation z-accessor">.</span><span class="z-entity z-name z-function">serve</span><span class="z-source">(</span><span class="z-punctuation">{</span></span>
<span class="giallo-l"><span class="z-source">  port</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-constant z-numeric"> 3000</span><span class="z-punctuation">,</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">  fetch</span><span class="z-punctuation">(</span><span class="z-variable z-parameter">req</span><span class="z-punctuation">) {</span></span>
<span class="giallo-l"><span class="z-keyword">    return</span><span class="z-keyword z-operator z-new"> new</span><span class="z-entity z-name z-function"> Response</span><span class="z-source">(</span><span class="z-punctuation z-definition z-string z-string">&quot;Hello, world!&quot;</span><span class="z-source">)</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-punctuation">  }</span></span>
<span class="giallo-l"><span class="z-punctuation">}</span><span class="z-source">)</span><span class="z-punctuation">;</span></span></code></pre><h3 id="inline-code">Inline Code</h3>
<p>As seen above, sometimes code items are mentioned in regular paragraphs, but you want to
draw attention to them. To do this, you can wrap it in back-tick (`) quotes. For
example, if I wanted to mention Rust’s <code>Vec&lt;T&gt;</code> type.</p>
<pre class="giallo z-code"><code data-lang="markdown"><span class="giallo-l"><span class="z-punctuation z-definition z-raw z-markdown z-markup z-inline z-raw z-string z-markdown">`Vec&lt;T&gt;`</span></span></code></pre>
<p>You can wrap a link around a code tag if you want to link to the docs, for example I could
link to the <a rel="noopener external" target="_blank" href="https://doc.rust-lang.org/std/option/enum.Option.html#method.take_if"><code>Option&lt;T&gt;::take_if</code></a>
method directly.</p>
<pre class="giallo z-code"><code data-lang="markdown"><span class="giallo-l"><span class="z-punctuation z-definition z-link z-title">[</span><span class="z-punctuation z-definition z-raw z-markdown z-markup z-inline z-raw z-string z-markdown">`Option&lt;T&gt;::take_if`</span><span class="z-punctuation z-definition z-link z-title">]</span><span class="z-punctuation">(</span><span class="z-markup z-underline z-link">https://doc.rust-lang.org/std/option/enum.Option.html#method.take_if</span><span class="z-punctuation">)</span></span></code></pre><h2 id="block-quotes">Block Quotes</h2>
<p>I can display a quote by prepending multiple lines of text with <code>&gt;</code> like so, which will
wrap it in a <code>blockquote</code> tag:</p>
<blockquote>
<p>“This text will appear italicized in a quote box!”</p>
</blockquote>
<h3 id="cited-quotations">Cited Quotations</h3>
<p>For when I want to have a citation, I can use the html <code>&lt;cite&gt;</code> tag after the quote text and it
will prepend it with a nice <code>—</code> em dash.</p>
<blockquote>
<p>“I don’t know half of you half as well as I should like, and I like less than half of you half
as well as you deserve.”</p>
<p><cite>Bilbo Baggins</cite></p>
</blockquote>
<h2 id="embedding-media">Embedding Media</h2>
<p>Images and videos are a great way to break up content and prevent text fatigue.</p>
<h3 id="images">Images</h3>
<p>Images can be embedded using the usual markdown syntax:</p>
<pre class="giallo z-code"><code data-lang="markdown"><span class="giallo-l"><span class="z-punctuation z-definition z-link">![</span><span class="z-string">alt text</span><span class="z-punctuation z-definition z-link">]</span><span class="z-punctuation">(</span><span class="z-markup z-underline z-link">/path/to/image.png</span><span class="z-punctuation">)</span></span></code></pre><figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;MZp-ye7LclCx.webp" alt="NOISE1 screenshot"  />
    </div>
    
</figure>
<p>When there are multiple paragraphs of text in a row (usually 3-4), and nothing else to break
them up, images can be interspersed to help prevent text-wall fatique.</p>
<p>You can also add captions to images:</p>
<pre class="giallo z-code"><code data-lang="plain"><span class="giallo-l"><span>{{ img(id=&quot;https://url.com/image.png&quot; alt=&quot;alt text&quot; caption=&quot;this can be ommited if you want or added! It&#39;s optional :)&quot;) }}</span></span></code></pre><figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;REmc3Tnp43hn.webp" alt="MacBook proprietary blade SSD"  />
    </div>
    
    <figcaption><p>it really was a rather sleek design; shame that apple got rid of it in favor of soldered on storage</p>
</figcaption>
    
</figure>
<p>You can also display multiple images side-by-side using the <code>imgs</code> shortcode with comma-separated URLs:</p>
<pre class="giallo z-code"><code data-lang="plain"><span class="giallo-l"><span>{{ imgs(id=&quot;https://url.com/image1.png, https://url.com/image2.png&quot; alt=&quot;alt text 1, alt text 2&quot; caption=&quot;optional caption for both images&quot;) }}</span></span></code></pre><figure class="center" >
    <div class="img-group" data-images="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;FPBdusjL9oIZ.webp, https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;FCjVs9QyX8jd.webp" data-alts="the copyright section, the ssh section">
    
    
    
        <div class="img-container" data-lightbox data-lightbox-group="group-https-l4-dunkirk-sh-i-fpbdusjl9oiz-webp-https-l4-dunkirk-sh-i-fcjvs9qyx8jd-webp">
            <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;FPBdusjL9oIZ.webp" alt="the copyright section"  />
        </div>
    
        <div class="img-container" data-lightbox data-lightbox-group="group-https-l4-dunkirk-sh-i-fpbdusjl9oiz-webp-https-l4-dunkirk-sh-i-fcjvs9qyx8jd-webp">
            <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;FCjVs9QyX8jd.webp" alt="the ssh section"  />
        </div>
    
    </div>
    
    <figcaption><p>side by side images from the remarkable tutorial</p>
</figcaption>
    
</figure>
<h3 id="videos">Videos</h3>
<p>To embed a video, you use the <code>youtube(id="", autoplay?=bool)</code> shortcode e.g.</p>
<figure class="yt-embed">
    <iframe
        src="https://www.youtube-nocookie.com/embed/NodwjZF7uZw"
        allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
        webkitallowfullscreen
        mozallowfullscreen
        allowfullscreen
    >
    </iframe>
    
</figure>
<h3 id="bluesky-posts">Bluesky posts</h3>
<p>This is handled by a shortcode <code>bluesky(post="")</code> and takes the post url as a parameter. These will automatically attach images and videos.</p>
           
<blockquote>
    First Day of summer is an annual public holiday in Iceland that is celebrated on the first Thursday after 18 April (some time between 19 and 25 April). It is a celebration of the start of the first summer month (Harpa) of the old Icelandic calendar.  
#Iceland #EastCoastKin #Photography #Summer   
    <div class="image-gallery gallery-grid">
        
        <img src="https:&#x2F;&#x2F;cdn.bsky.app&#x2F;img&#x2F;feed_fullsize&#x2F;plain&#x2F;did:plc:k3lj3tnwkpllygj4xfrherjm&#x2F;bafkreiay6e3siviuqp6nakdhngxvcsbt4vkn5ayxfp6jpyjebfa2xlgyee" alt="" loading="lazy" />
        
        <img src="https:&#x2F;&#x2F;cdn.bsky.app&#x2F;img&#x2F;feed_fullsize&#x2F;plain&#x2F;did:plc:k3lj3tnwkpllygj4xfrherjm&#x2F;bafkreibazbea22lxcurtd7wuogbqhaa22akvtnu6m3ktkv5bymkh464itm" alt="" loading="lazy" />
        
        <img src="https:&#x2F;&#x2F;cdn.bsky.app&#x2F;img&#x2F;feed_fullsize&#x2F;plain&#x2F;did:plc:k3lj3tnwkpllygj4xfrherjm&#x2F;bafkreieyypvllfvjiivbwxo4eznh3s4d7dyyvp2phrrp65jbvquen5iyne" alt="" loading="lazy" />
        
        <img src="https:&#x2F;&#x2F;cdn.bsky.app&#x2F;img&#x2F;feed_fullsize&#x2F;plain&#x2F;did:plc:k3lj3tnwkpllygj4xfrherjm&#x2F;bafkreic7xh525j6tcrsqlrqxzmgfzlz6r6pqj4foli2yiwgraysxs5a5ou" alt="" loading="lazy" />
        
    </div>
      
</blockquote>
<p>
    <cite>
        <a href="https:&#x2F;&#x2F;bsky.app&#x2F;profile&#x2F;svenninifl.bsky.social&#x2F;post&#x2F;3lnkivz3ans2k" target="_blank" rel="noopener"
            ><img
                src="https:&#x2F;&#x2F;cdn.bsky.app&#x2F;img&#x2F;avatar&#x2F;plain&#x2F;did:plc:k3lj3tnwkpllygj4xfrherjm&#x2F;bafkreidn5sjjqnhwjpo77jes6unxloewjykjbwovw7qx5vrdx25jecwzbe"
                alt="📷 Sveinn Nifl 📷 🇮🇸 Landscape and nature photos's avatar"
                class="avatar"
            />@svenninifl.bsky.social</a
        ></cite
    >
</p>

<h2 id="miscellaneous">Miscellaneous</h2>
<p>You can also create <code>&lt;hr&gt;</code> horizontal rule tags using <code>---</code> in markdown, like so:</p>
<hr />
<p>But these should be used sparingly, if at all.</p>
<p>You can also use emojis inline from the hackclub slack like this :yay:! This is just done by writing <code>:emoji:</code> and it gets progressively enhanced with a bit of js as long as the emoji is in cachet!</p>
<h2 id="callouts">Callouts</h2>
<p>Callouts are a great way to draw attention to important information. They come in several types:</p>
<h3 id="info-callout">Info Callout</h3>
<div class="callout callout-blue">
  <div class="callout-title">
    <span class="callout-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg></span>
    <strong>Info</strong>
  </div>
  <div class="callout-content">
    <p>This is an info callout! Use this for general information that readers should be aware of.</p>

  </div>
</div>
<h3 id="warning-callout">Warning Callout</h3>
<div class="callout callout-yellow">
  <div class="callout-title">
    <span class="callout-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"></path><line x1="12" y1="9" x2="12" y2="13"></line><line x1="12" y1="17" x2="12.01" y2="17"></line></svg></span>
    <strong>Warning</strong>
  </div>
  <div class="callout-content">
    <p>This is a warning callout! Use this to alert readers about potential issues or things to watch out for.</p>

  </div>
</div>
<h3 id="danger-callout">Danger Callout</h3>
<div class="callout callout-red">
  <div class="callout-title">
    <span class="callout-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="15" y1="9" x2="9" y2="15"></line><line x1="9" y1="9" x2="15" y2="15"></line></svg></span>
    <strong>Danger</strong>
  </div>
  <div class="callout-content">
    <p>This is a danger callout! Use this for critical information that could cause problems if ignored.</p>

  </div>
</div>
<h3 id="tip-callout">Tip Callout</h3>
<div class="callout callout-green">
  <div class="callout-title">
    <span class="callout-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5"></path><path d="M9 18h6"></path><path d="M10 22h4"></path></svg></span>
    <strong>Tip</strong>
  </div>
  <div class="callout-content">
    <p>This is a tip callout! Use this to share helpful hints and best practices.</p>

  </div>
</div>
<h3 id="note-callout">Note Callout</h3>
<div class="callout callout-gray">
  <div class="callout-title">
    <span class="callout-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20h9"></path><path d="M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4Z"></path></svg></span>
    <strong>Note</strong>
  </div>
  <div class="callout-content">
    <p>This is a note callout! Use this for additional context or side information.</p>

  </div>
</div>
<h3 id="custom-title">Custom Title</h3>
<div class="callout callout-blue">
  <div class="callout-title">
    <span class="callout-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg></span>
    <strong>Custom Title Here</strong>
  </div>
  <div class="callout-content">
    <p>You can also customize the title of any callout by adding a <code>title</code> parameter!</p>

  </div>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>Blade SSD removal MBP 2017</title>
      <link>https://dunkirk.sh/blog/ssd-removal-mbp-2017/</link>
      <guid isPermaLink="true">https://dunkirk.sh/blog/ssd-removal-mbp-2017/</guid>
      <pubDate>Sat, 03 Aug 2024 00:00:00 +0000</pubDate>
      
      
      <dc:creator>Kieran Klukas</dc:creator>
      
      
        
      <category>tutorial</category>
        
      <category>teardown</category>
        
      <category>archival</category>
        
      
      <description>
        
          &amp;lt;p&amp;gt;Hi! I’ve had a MacBook Pro 2017 for about a year now, and I got it used; it’s been great so far until one day after updating it just refused to turn on I’m not entirely sure why this happened, but I replaced the battery and that didn’t solve the issue so yeah ^_^&amp;lt;&#x2F;p&amp;gt;
        
      </description>
      <content:encoded><![CDATA[<p>Hi! I’ve had a MacBook Pro 2017 for about a year now, and I got it used; it’s been great so far until one day after updating it just refused to turn on I’m not entirely sure why this happened, but I replaced the battery and that didn’t solve the issue so yeah ^_^</p>
<span id="continue-reading"></span><figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;REmc3Tnp43hn.webp" alt="MacBook proprietary blade SSD"  />
    </div>
    
    <figcaption><p>it really was a rather sleek design; shame that apple got rid of it in favor of soldered on storage</p>
</figcaption>
    
</figure>
<p>I eventually decided to just try and remove the SSD from the MacBook and see if there was a way to recover any files from it (spoiler: there kinda is, but it’s annoying) but I couldn’t find any guide online and iFixit had nothing. So I decided to just try and yolo it and see if I could figure it out on my own, and surprisingly I actually managed to do it! Turns out,  the process isn’t that hard! I’ll take you through the steps I took so that if you want to do this, it’s much less of a hassle.</p>
<h2 id="guide">Guide</h2>
<ol>
<li>
<p>the first thing you need to do is to remove the screws from the back of your MacBook. This will use a P5 Pentalobe driver, which I believe you can buy from iFixit as well as several other companies on Amazon.
<figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;QtJUiXMSX-lY.webp" alt="Removing the screws"  />
    </div>
    
</figure>
</p>
</li>
<li>
<p>next you need to crack open the shell of the MacBook by prying under the front (on the side where the MacBook opens). It’s pretty helpful to have a suction cup or something to lift it up a bit so you can get your prying tool underneath (I used a flat plastic prying tool I got from the battery repair kit for this MacBook, but a guitar pick or credit card would probably also work)
<figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;GdbMMR4JBh6x.webp" alt="using a suction cup to lift the back shell"  />
    </div>
    
</figure>
</p>
</li>
<li>
<p>now once you’ve got the back slightly opened up just run around the edge of the shell prying up on it until the front and two sides are free then just pull forward at a slight (15ish degree?) angle, and it should slide right out.
<figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;vrohFehEFZ0r.webp" alt="the opened MacBook"  />
    </div>
    
</figure>
</p>
</li>
<li>
<p>once it’s open, locate the silver metal block looking thing; this is your SSD
<figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;y2s-Zcdt-FL6.webp" alt="the SSD"  />
    </div>
    
</figure>
</p>
</li>
<li>
<p>now using a T5 Torx driver (why couldn’t you just use one type of screws apple 😭; be more like framework) you need to unscrew the two screws on either side of the front of the SSD
<figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;brqIWi0VeSAX.webp" alt="the screws"  />
    </div>
    
</figure>
</p>
</li>
<li>
<p>now comes the slightly scary part (for me at least) you need to lift the black tape that’s covering the top of the SSD (don’t worry the SSD will be fine)
<figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;aG0xcxZMN9Kq.webp" alt="the removed tape on the SSD"  />
    </div>
    
</figure>
</p>
</li>
<li>
<p>now just slightly pull on the SSD (again at a slight angle) and it should pop right out!
<figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;RlA6xfMC8qdi.webp" alt="the SSD out of the MacBook"  />
    </div>
    
</figure>
</p>
</li>
</ol>
<h2 id="postlog-and-notes">Postlog and notes</h2>
<p>I hope this helped if you are trying to do this your self! Now for recovering the data the two options I’ve found are a) buy a secondary MacBook of the exact same generation and model and swap your SSD in or b) pay some data recovery company a lot of money to probably do the same thing for you; neither option is super appealing to me, so I’ll keep searching for alternatives and I will be sure to update this article if I do find any. As of today though (August 3rd 2024) I haven’t been able to get a hold of another MacBook or adaptor to connect this to my computer but if you do find one definitely leave a comment on the hacker news post linked below!</p>
<ul>
<li>Posted on HackerNews on <code>2024-08-03</code> <a rel="noopener external" target="_blank" href="https://news.ycombinator.com/item?id=41147359">hn://item/41147359</a></li>
<li>Republished to this blog on <code>2024-10-31</code> with minor edits</li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Monaspace VS-Code install</title>
      <link>https://dunkirk.sh/blog/monaspace-vs-code-install/</link>
      <guid isPermaLink="true">https://dunkirk.sh/blog/monaspace-vs-code-install/</guid>
      <pubDate>Fri, 10 Nov 2023 00:00:00 +0000</pubDate>
      
      
      <dc:creator>Kieran Klukas</dc:creator>
      
      
        
      <category>tutorial</category>
        
      <category>archival</category>
        
      
      <description>
        
          &amp;lt;p&amp;gt;To install the Monaspace font on macOS (or windows or linux) with VS Code and enable multifont syntax highlighting with the &amp;lt;a rel=&amp;quot;noopener external&amp;quot; target=&amp;quot;_blank&amp;quot; href=&amp;quot;https:&#x2F;&#x2F;marketplace.visualstudio.com&#x2F;items?itemName=be5invis.vscode-custom-css&amp;quot;&amp;gt;CSS JS Loader extension&amp;lt;&#x2F;a&amp;gt;, you can follow these steps:&amp;lt;&#x2F;p&amp;gt;
        
      </description>
      <content:encoded><![CDATA[<p>To install the Monaspace font on macOS (or windows or linux) with VS Code and enable multifont syntax highlighting with the <a rel="noopener external" target="_blank" href="https://marketplace.visualstudio.com/items?itemName=be5invis.vscode-custom-css">CSS JS Loader extension</a>, you can follow these steps:</p>
<span id="continue-reading"></span><figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;Y4cEqfeAHBYR.webp" alt="monaspace font in action"  />
    </div>
    
    <figcaption><p>This font is so pretty and has so many features its amazing. It’s main downside is to work it takes to set it up.</p>
</figcaption>
    
</figure>
<h2 id="1-download-and-install-the-monaspace-font">1. Download and install the Monaspace font:</h2>
<p>First visit <a rel="noopener external" target="_blank" href="https://github.com/githubnext/monaspace/releases/latest">https://github.com/githubnext/monaspace/releases/latest</a> and download the zip.
Next to install the Monaspace font:</p>
<ul>
<li>On macOS, drag the font files into font book.</li>
<li>For windows, drag into the font window in settings.</li>
<li>For Linux, clone the repo and run: <code>cd util; ./install_linux.sh</code></li>
</ul>
<h2 id="2-configure-vs-code">2. Configure VS Code</h2>
<p>Install the <a rel="noopener external" target="_blank" href="https://marketplace.visualstudio.com/items?itemName=be5invis.vscode-custom-css">Custom CSS and JS Loader</a> plugin.
Set the font to one of the following options: <code>Monaspace Neon Var</code>, <code>Monaspace Argon Var</code>, <code>Monaspace Xeon Var</code>, <code>Monaspace Radon Var</code>, or <code>Monaspace Krypton Var</code>.</p>
<ul>
<li>You will find this option under <em>Editor: Font Family</em> in the user preferences</li>
</ul>
<figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;JZ0hERyiZhmR.webp" alt="the available varients of the font"  />
    </div>
    
</figure>
<p>Next enable font ligatures in the settings.json with following snippet:</p>
<blockquote>
<p>settings.json</p>
</blockquote>
<pre class="giallo z-code"><code data-lang="json"><span class="giallo-l"><span class="z-punctuation z-definition z-string z-string">&quot;editor.fontLigatures&quot;</span><span class="z-source">: </span><span class="z-punctuation z-definition z-string z-string">&quot;&#39;ss01&#39;, &#39;ss02&#39;, &#39;ss03&#39;, &#39;ss04&#39;, &#39;ss05&#39;, &#39;ss06&#39;, &#39;ss07&#39;, &#39;ss08&#39;, calt&#39;, &#39;dlig&#39;&quot;</span><span class="z-source">,</span></span></code></pre>
<p>Now enable the custom CSS file within the <code>settings.json</code>, modifying the file path for Windows / MacOS / Linux if needed:</p>
<blockquote>
<p>still settings.json</p>
</blockquote>
<pre class="giallo z-code"><code data-lang="json"><span class="giallo-l"><span class="z-punctuation z-definition z-string z-string">&quot;vscode_custom_css.imports&quot;</span><span class="z-source">: </span><span class="z-punctuation">[</span></span>
<span class="giallo-l"><span class="z-punctuation z-definition z-string z-string">    &quot;file:///Users/{{user}}/.vscode/style.css&quot;</span><span class="z-punctuation">,</span><span class="z-punctuation z-definition z-comment z-comment"> // for mac (remove if not mac)</span></span>
<span class="giallo-l"><span class="z-punctuation z-definition z-string z-string">    &quot;file://C://Users/{{user}}/vscode/style.css&quot;</span><span class="z-punctuation z-definition z-comment z-comment"> // for windows (remove if not windows)</span></span>
<span class="giallo-l"><span class="z-punctuation z-definition z-string z-string">    &quot;file:///home/{{user}}/.vscode/style.css&quot;</span><span class="z-punctuation z-definition z-comment z-comment"> // for linux (remove if not windows)</span></span>
<span class="giallo-l"><span class="z-punctuation">]</span><span class="z-source">,</span></span></code></pre><h2 id="3-create-custom-css-file-at-the-path-you-specified-above">3. Create custom CSS file at the path you specified above.</h2>
<p>Depending on your VS Code version, the class names might be different, so you may need to use the developer tools to find the correct one.
The styles that worked for me on <code>VS Code version: 1.84.2 (Universal) commit: 1a5daa3a0231a0fbba4f14db7ec463cf99d7768e</code> are here:</p>
<blockquote>
<p>style.css</p>
</blockquote>
<pre class="giallo z-code"><code data-lang="css"><span class="giallo-l"><span class="z-punctuation z-definition z-comment z-comment">/* Comment Class */</span></span>
<span class="giallo-l"><span class="z-punctuation">.</span><span class="z-entity z-other z-attribute-name z-class z-css">mtk3</span><span class="z-punctuation"> {</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">    font-family</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Monaspace Radon Var&quot;</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">    font-weight</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-constant z-numeric"> 500</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-punctuation">}</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-punctuation z-definition z-comment z-comment">/* Copilot Classes */</span></span>
<span class="giallo-l"><span class="z-punctuation">.</span><span class="z-entity z-other z-attribute-name z-class z-css">ghost-text-decoration</span><span class="z-punctuation"> {</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">    font-family</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Monaspace Krypton Var&quot;</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">    font-weight</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-constant z-numeric"> 200</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-punctuation">}</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-punctuation">.</span><span class="z-entity z-other z-attribute-name z-class z-css">ghost-text-decoration-preview</span><span class="z-punctuation"> {</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">    font-family</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Monaspace Krypton Var&quot;</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-support z-type z-property-name z-css">    font-weight</span><span class="z-punctuation z-separator z-key-value">:</span><span class="z-constant z-numeric"> 200</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-punctuation">}</span></span></code></pre>
<p><em>Thanks to <strong><a rel="noopener external" target="_blank" href="https://github.com/fspoettel">@fspoettel</a></strong> on GitHub for this trick to get the copilot classes when in dev mode</em></p>
<blockquote>
<p>“You can inspect transient DOM elements by halting the app with a <code>debugger</code> after a delay with a debugger call inside a <code>setTimeout</code>.”</p>
<p><cite><a rel="noopener external" target="_blank" href="https://github.com/fspoettel">@fspoettel</a></cite></p>
</blockquote>
<p>You can copy the following snippet to do just that!</p>
<blockquote>
<p>console</p>
</blockquote>
<pre class="giallo z-code"><code data-lang="typescript"><span class="giallo-l"><span class="z-entity z-name z-function">setTimeout</span><span class="z-source">(</span><span class="z-punctuation">()</span><span class="z-storage z-type z-function z-arrow z-ts"> =&gt;</span><span class="z-punctuation"> {</span></span>
<span class="giallo-l"><span class="z-keyword">    debugger</span><span class="z-punctuation">;</span></span>
<span class="giallo-l"><span class="z-punctuation">},</span><span class="z-constant z-numeric"> 10000</span><span class="z-source">)</span><span class="z-punctuation">;</span></span></code></pre>
<p>Before you are finished make sure you have run the <code>Enable Custom CSS and JS</code> command from the command bar.</p>
<h2 id="closing-remarks">Closing Remarks</h2>
<p>That should be it! Hopefully you will have a beautiful custom font VS Code install.</p>
<p>If you are looking for a good theme, I can highly recommend the <a rel="noopener external" target="_blank" href="https://marketplace.visualstudio.com/items?itemName=Catppuccin.catppuccin-vsc">Catppuccin</a> theme, as that is what I use myself. Be sure to check out <a rel="noopener external" target="_blank" href="https://monaspace.githubnext.com/">Monaspace’s website</a> as it is a work of art. Happy Coding! 👩‍💻</p>
<ul>
<li><em>Updated 2024-08-22: changed mtk4 to mtk3 on the feedback of <a rel="noopener external" target="_blank" href="https://github.com/mutammim">mutammim</a></em></li>
<li><em>Updated 2024-10-31: changed around the formatting of the post and moved to <a rel="noopener external" target="_blank" href="https://dunkirk.sh">dunkirk.sh</a></em></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Censorship or Protection?</title>
      <link>https://dunkirk.sh/blog/analyzing-implications-of-online-safety-legislation/</link>
      <guid isPermaLink="true">https://dunkirk.sh/blog/analyzing-implications-of-online-safety-legislation/</guid>
      <pubDate>Wed, 01 Nov 2023 00:00:00 +0000</pubDate>
      
      
      <dc:creator>Kieran Klukas</dc:creator>
      
      
        
      <category>essays</category>
        
      <category>archival</category>
        
      
      <description>
        
          
    
        
    
    
    Law makers keeping producing new “online safety bills” but do they really help?

    

In the last few years, we have seen a wave of “online safety bills” created by lawmakers that will ostensibly help protect chil…
        
      </description>
      <content:encoded><![CDATA[<span id="continue-reading"></span><figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;ftoCrqy9S3-o.webp" alt="child looking out window"  />
    </div>
    
    <figcaption><p>Law makers keeping producing new “online safety bills” but do they really help?</p>
</figcaption>
    
</figure>
<p>In the last few years, we have seen a wave of “online safety bills” created by lawmakers that will ostensibly help protect children online. The US has the Protecting Kids on Social Media Act (PKSMA, S.1291) and the Kids Online Safety Act (KOSA, S.1409) while in the UK they have the Online Safety Bill (OSB). The main feature that all of these bills have in common is the censorship of online content for minors. The Electronic Frontier Foundation (EFF) has raised concerns over KOSA, saying, “The bill requires all websites, apps, and online platforms to filter and block legal speech” (Mullin). These bills raise an important question–should the government regulate the online activities of children, or should that responsibility lie solely with parents?</p>
<p>The bills above all propose different methods of regulating online content, but they all contain a united mission to “protect children online.” KOSA has four regulatory pieces. One of them is the legal duty for platforms to prevent certain harms including “promotion of self-harm, suicide, eating disorders, substance abuse, and other matters that pose a risk to the physical and mental health of a minor” (“Text - S.1409”). This would cover any platform that is “reasonably likely to be used” (“Text - S.1409”) by a minor, and would be supplemented with strict parental controls required by default. As writers from the EFF pointed out, as a result of the broad language the potential impact could be very wide, affecting teaching on subjects such as gun ownership, Christianity, racism, substance abuse, eating disorders, and depression to name a few (Kelley) (Mullin). As Sarah Philips, organizer of Fight for the Future, so aptly, put it, “We don’t live in a country where there is a consensus about what is harmful to children, so how could the government determine what’s appropriate for every kid” (qtd. in Molloy)?</p>
<p>KOSA would also compel platforms to provide data to researchers and implement an elaborate age-verification system. This age verification clause is a problem because children could be made federal criminals and/or be fined, or imprisoned for up to five years, for circumventing the system (Weissmann). A survey by Internet Matters of services with a minimum age of 13, revealed that of 1000 11-15-year-olds, 62% of 11-year-olds and 69% of 12-year-olds have a Facebook profile, 36% of 11-year-olds and 57% of 12-year-olds use Instagram, 22% of 11-year-olds and 41% of 12-year-olds own a Snapchat account. In addition, 50% of the children surveyed had a WhatsApp account, which has a minimum age of 16.</p>
<p>PKSMA would go even further than KOSA by prohibiting kids under 13 from using social media at all and requiring any minor under 18 to have parental permission to create an account. To enforce the age limit, the government would create a digital ID system and companies would be required to use the system to verify users. While the system is still being developed, and we don’t have definite information on how it would work, we do know that the program would be implemented as a barrier to creating an account. As a result, it would restrict access to platforms and services from people who don’t have a form of government identification. According to a Brennan Center study of voting age adults, this could negatively affect 11% of Americans, potentially higher when minors, and undocumented immigrants are included. Considering that these platforms are worldwide, the problem of having to create a global digital ID system or more likely a location system is encountered. This would be difficult to implement as a location based system can easily be tricked, and a global digital ID is unlikely.</p>
<p>The OSB from the UK has the same digital ID and content censorship requirements as the other two bills but contains an even scarier proposal: government mandated surveillance of every message, post, or piece of content generated by users. This would effectively kill end-to-end encryption and generate massive safety risks for those that use online services to avoid persecution or censorship in their countries. The response given by the House of Lords against the furious pushback has been, according to the EFF, “to wave its hands and deny reality. … the U.K.’s Minister for Culture, Media and Sport simply re-hashes an imaginary world in which messages can be scanned while user privacy is maintained” (Mullin, “The UK Government”).</p>
<p>A potential obstacle to these bills, however, is that the restriction of content for minors is unlawful. In the USA, the First Amendment, a key piece of our civil liberties since 1791 as a part of the Bill of Rights, protects the right to free speech with these words: “Congress shall make no law …abridging the freedom of speech.” For the rest of the world, the ICCPR, developed by the United Nations to protect freedoms globally, states in Article 19: “Everyone shall have the right to hold opinions without interference. Everyone shall have the right to freedom of expression; this right shall include freedom to seek, receive and impart information and ideas of all kinds, regardless of frontiers, either orally, in writing or in print, in the form of art, or through any other media of his choice” (“International Covenant on Civil and Political Rights”). Neither of these provide a qualifying age factor. As the Supreme Court stated in the case Planned Parenthood v. Danforth: “Constitutional rights do not mature and come into being magically only when one attains the state-defined age of majority. Minors, as well as adults, are protected by the Constitution and possess constitutional rights” (Planned Parenthood v. Danforth, 428 U.S. 52 (1976)).</p>
<p>In Modern Socio-Technical Perspectives on Privacy, the authors say, “young people place great social value on their online privacy and want policies that are fair and negotiable.” and in addition, “The use of online surveillance … can undermine trust and hinder relationship building” (Wisniewski et al.). This is specifically speaking of the relationship between parent and child, but more broadly can be extrapolated to the government. We don’t want the next generation of our youth to grow up feeling like the government doesn’t trust them or their parents to regulate their information intake. Youth won’t be able to learn and grow from interacting with others online if the government walls it off.</p>
<p>Many of the systems that lawmakers are trying to put in place might not be effective. “According to Yardi and Bruckman, teens—who often have greater knowledge and skills … in applying technology workarounds—may rebel against … rules and engage in riskier behaviors to avoid technology constraints” (Wisniewski et al.). This could mean that the main effect of those laws would be to censor the content of everyone using social platforms, while completely failing to keep minors off social media.</p>
<p>In summary, these bills all have the same stated goal: to protect minors online, a laudable goal. The way lawmakers are trying to do this, however, creates many harmful implications both for minors and for adults. We don’t need the government to make decisions on what content is or isn’t acceptable or what age they should be allowed to use services. Instead, I would propose that the government help provide tools and resources that parents are not obliged to use, but which they may use to help their children better understand and navigate the online world.</p>
<hr />
<h2 id="works-cited">Works Cited</h2>
<p>Brennan Center. <em>Citizens Without Proof</em>. <a rel="noopener external" target="_blank" href="https://www.brennancenter.org/our-work/research-reports/citizens-without-proof">https://www.brennancenter.org/our-work/research-reports/citizens-without-proof</a>. Accessed 27 Sept. 2023.</p>
<p>“International Covenant on Civil and Political Rights.” <em>OHCHR</em>, <a rel="noopener external" target="_blank" href="https://www.ohchr.org/en/instruments-mechanisms/instruments/international-covenant-civil-and-political-rights"></a><a rel="noopener external" target="_blank" href="https://www.brennancenter.org/our-work/research-reports/citizens-without-proof">https://www.ohchr.org/en/instruments-mechanisms/instruments/international-covenant-civil-and-political-rights</a>. Accessed 27 Sept. 2023.</p>
<p>Internet Matters. “Should Social Media Use Age Verification?” <em>Internet Matters</em>, 13 June 2016, <a rel="noopener external" target="_blank" href="https://www.internetmatters.org/hub/expert-opinion/digital-doormen-dont-ask-for-id/"></a><a rel="noopener external" target="_blank" href="https://www.brennancenter.org/our-work/research-reports/citizens-without-proof">https://www.internetmatters.org/hub/expert-opinion/digital-doormen-dont-ask-for-id/</a>.</p>
<p>Kelley, Jason. “The Kids Online Safety Act Is Still A Huge Danger to Our Rights Online.” <em>Electronic Frontier Foundation</em>, 2 May 2023, <a rel="noopener external" target="_blank" href="https://www.eff.org/deeplinks/2023/05/kids-online-safety-act-still-huge-danger-our-rights-online"></a><a rel="noopener external" target="_blank" href="https://www.brennancenter.org/our-work/research-reports/citizens-without-proof">https://www.eff.org/deeplinks/2023/05/kids-online-safety-act-still-huge-danger-our-rights-online</a>.</p>
<p>Molloy, Parker. <em>Congress Is About to Pass a Very Bad Internet Bill. Here’s How You Can Stop It.</em> 2 June 2022, <a rel="noopener external" target="_blank" href="https://www.readtpa.com/p/congress-is-about-to-pass-a-very"></a><a rel="noopener external" target="_blank" href="https://www.brennancenter.org/our-work/research-reports/citizens-without-proof">https://www.readtpa.com/p/congress-is-about-to-pass-a-very</a>.</p>
<p>Mullin, Joe. “Congress Amended KOSA, But It’s Still A Censorship Bill.” <em>Electronic Frontier Foundation</em>, 10 Aug. 2023, <a rel="noopener external" target="_blank" href="https://www.eff.org/deeplinks/2023/08/congress-amended-kosa-its-still-censorship-bill"></a><a rel="noopener external" target="_blank" href="https://www.brennancenter.org/our-work/research-reports/citizens-without-proof">https://www.eff.org/deeplinks/2023/08/congress-amended-kosa-its-still-censorship-bill</a>.</p>
<p>-–. “The UK Government Knows How Extreme The Online Safety Bill Is.” <em>Electronic Frontier Foundation</em>, 8 Sept. 2023, <a rel="noopener external" target="_blank" href="https://www.eff.org/deeplinks/2023/09/uk-government-knows-how-extreme-online-safety-bill"></a><a rel="noopener external" target="_blank" href="https://www.brennancenter.org/our-work/research-reports/citizens-without-proof">https://www.eff.org/deeplinks/2023/09/uk-government-knows-how-extreme-online-safety-bill</a>.</p>
<p>“Planned Parenthood v. Danforth, 428 U.S. 52 (1976).” <em>Justia Law</em>, <a rel="noopener external" target="_blank" href="https://supreme.justia.com/cases/federal/us/428/52/"></a><a rel="noopener external" target="_blank" href="https://www.brennancenter.org/our-work/research-reports/citizens-without-proof">https://supreme.justia.com/cases/federal/us/428/52/</a>. Accessed 27 Sept. 2023.</p>
<p>“Text - S.1409 - 118th Congress (2023-2024): Kids Online Safety Act.” Congress.gov, Library of Congress, 27 July 2023, <a rel="noopener external" target="_blank" href="https://www.brennancenter.org/our-work/research-reports/citizens-without-proof">https://www.congress.gov/bill/118th-congress/senate-bill/1409/text</a>.</p>
<p>Weissmann, Shoshana. “Regimes That Run Age Verification through the Government Would Allow Prosecutors to Make Children Federal Criminals If They Lie about Their Age.” <em>R Street Institute</em>, <a rel="noopener external" target="_blank" href="https://www.rstreet.org/commentary/regimes-that-run-age-verification-through-the-government-would-allow-prosecutors-to-make-children-federal-criminals-if-they-lie-about-their-age/"></a><a rel="noopener external" target="_blank" href="https://www.brennancenter.org/our-work/research-reports/citizens-without-proof">https://www.rstreet.org/commentary/regimes-that-run-age-verification-through-the-government-would-allow-prosecutors-to-make-children-federal-criminals-if-they-lie-about-their-age/</a>. Accessed 14 Sept. 2023.</p>
<p>Wisniewski, Pamela J., et al. “Privacy in Adolescence.” <em>Modern Socio-Technical Perspectives on Privacy</em>, edited by Bart P. Knijnenburg et al., Springer International Publishing, 2022, pp. 315–36. <em>Springer Link</em>, <a rel="noopener external" target="_blank" href="https://doi.org/10.1007/978-3-030-82786-1_14"></a><a rel="noopener external" target="_blank" href="https://www.brennancenter.org/our-work/research-reports/citizens-without-proof">https://doi.org/10.1007/978-3-030-82786-1_14</a>.</p>
<ul>
<li>Written on <code>2023-11-01</code> and republished to this blog on <code>2024-10-31</code></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Garmin Vivoactive 4 with Home Assistant</title>
      <link>https://dunkirk.sh/blog/garmin-vivoactive-homeassistant/</link>
      <guid isPermaLink="true">https://dunkirk.sh/blog/garmin-vivoactive-homeassistant/</guid>
      <pubDate>Fri, 04 Aug 2023 00:00:00 +0000</pubDate>
      
      
      <dc:creator>Kieran Klukas</dc:creator>
      
      
        
      <category>essays</category>
        
      <category>archival</category>
        
      
      <description>
        
          &amp;lt;p&amp;gt;This morning I saw a &amp;lt;a rel=&amp;quot;noopener external&amp;quot; target=&amp;quot;_blank&amp;quot; href=&amp;quot;https:&#x2F;&#x2F;libreddit.kieranklukas.com&#x2F;r&#x2F;flipperzero&#x2F;comments&#x2F;ybjsvt&#x2F;flipper_control_via_smartwatch&#x2F;&amp;quot;&amp;gt;Reddit post&amp;lt;&#x2F;a&amp;gt; where someone connected their flipper zero to a Fossil HR through &amp;lt;a rel=&amp;quot;noopener external&amp;quot; target=&amp;quot;_blank&amp;quot; href=&amp;quot;https:&#x2F;&#x2F;gadgetbridge.org&#x2F;&amp;quot;&amp;gt;Gadgetbridge&amp;lt;&#x2F;a&amp;gt;. I immediately started &amp;lt;a rel=&amp;quot;noopener external&amp;quot; target=&amp;quot;_blank&amp;quot; href=&amp;quot;https:&#x2F;&#x2F;libreddit.kieranklukas.com&#x2F;r&#x2F;duckduckgo&#x2F;wiki&#x2F;index#wiki_what_is_searching_on_duckduckgo_called.3F&amp;quot;&amp;gt;ducking,&amp;lt;&#x2F;a&amp;gt; trying to find out if I could do the same with my Garmin Vivoactive 4 but ended up realizing that there was no apparent way to connect the two. I did however find a widget compatible with my watch named &amp;lt;a rel=&amp;quot;noopener external&amp;quot; target=&amp;quot;_blank&amp;quot; href=&amp;quot;https:&#x2F;&#x2F;apps.garmin.com&#x2F;en-US&#x2F;apps&#x2F;ac9a81ab-a52d-41b3-8c14-940a9de37544&amp;quot;&amp;gt;APICall&amp;lt;&#x2F;a&amp;gt; on the Connect IQ store.&amp;lt;&#x2F;p&amp;gt;
        
      </description>
      <content:encoded><![CDATA[<p>This morning I saw a <a rel="noopener external" target="_blank" href="https://libreddit.kieranklukas.com/r/flipperzero/comments/ybjsvt/flipper_control_via_smartwatch/">Reddit post</a> where someone connected their flipper zero to a Fossil HR through <a rel="noopener external" target="_blank" href="https://gadgetbridge.org/">Gadgetbridge</a>. I immediately started <a rel="noopener external" target="_blank" href="https://libreddit.kieranklukas.com/r/duckduckgo/wiki/index#wiki_what_is_searching_on_duckduckgo_called.3F">ducking,</a> trying to find out if I could do the same with my Garmin Vivoactive 4 but ended up realizing that there was no apparent way to connect the two. I did however find a widget compatible with my watch named <a rel="noopener external" target="_blank" href="https://apps.garmin.com/en-US/apps/ac9a81ab-a52d-41b3-8c14-940a9de37544">APICall</a> on the Connect IQ store.</p>
<span id="continue-reading"></span><figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;9xmYg_HE3KCn.webp" alt="a garmin watch with the apicall app open to a spotify page"  />
    </div>
    
    <figcaption><p>I can control spotify from my watch via api hooks how bout you?</p>
</figcaption>
    
</figure>
<p>This widget interested me because it allowed me to call any webhook I wanted utilizing the onboard Wi-Fi as well as through the Connect IQ app. This was a very important feature for me because I can’t get the app to run on LineageOS as it keeps asking for the location permission even though it was already granted.</p>
<p>My first idea was to try to broadcast a message to the Google home using <a rel="noopener external" target="_blank" href="https://github.com/ismarslomic/google-assistant-broadcast">ismarslomic/google-assistant-broadcast,</a> but it ended up being broken. I decided, therefore, that since the project was unmaintained to try Home Assistant with the <a rel="noopener external" target="_blank" href="https://www.home-assistant.io/integrations/google_assistant_sdk#configuration">Google Assistant SDK</a>.</p>
<p>The setup was amazingly quick, using the <a rel="noopener external" target="_blank" href="https://github.com/linuxserver/docker-homeassistant">linuxserver/docker-homeassistant</a> image and their sample compose file, I was able to get it fully running in under 10 minutes.</p>
<p>Now for the Google Assistant SDK / APICall / Home Assistant tutorial. The first thing you want to do is follow this guide, <a rel="noopener external" target="_blank" href="https://www.home-assistant.io/integrations/google_assistant_sdk#configuration">Google Assistant SDK - Home Assistant</a>, to install the Assistant SDK. Once you have completed that, go to Settings / Automations &amp; Services.</p>
<p><figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;ibVO3Z1trZhn.webp" alt="arrow pointing to settings in home assistant"  />
    </div>
    
</figure>

<figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;ZGVec6P_sHUb.webp" alt="arrow pointing to Automations &amp; Services in home assistant"  />
    </div>
    
</figure>
</p>
<p>This is where you can create the action that you want to trigger with your smartwatch. The first thing you need to do is to create a new automation. Save and name the automation you just created. Now add a trigger, scroll to the bottom of the list and select webhook. If done successfully, it will look like the image below.</p>
<figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;3EZvrvOFVNDk.webp" alt="creating a new webhook in home assistant"  />
    </div>
    
</figure>
<p>Now add an action. I decided to use the media player to play a song on Spotify. Also go back to the webhook section and click the settings icon next to the webhook ID. Change the settings to reflect below screenshot.</p>
<p><figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;txErwYEwd9N2.webp" alt="editing the webook in home assistant to allow GET queries"  />
    </div>
    
</figure>

<figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;5kxiONy6zY28.webp" alt="adding a play media block to the webhook"  />
    </div>
    
</figure>
</p>
<p>Now for the fun part. Download <a rel="noopener external" target="_blank" href="https://apps.garmin.com/en-US/apps/ac9a81ab-a52d-41b3-8c14-940a9de37544">APICall</a> onto your Garmin smartwatch and go to the configuration section for the app.</p>
<blockquote>
<p>Note: I’ll be using Garmin Express on my MacBook, but you can also use the Garmin Connect app on a phone.</p>
</blockquote>
<figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;7YU--efbSV7Y.webp" alt="garmin express app homepage on desktop"  />
    </div>
    
</figure>
<p>If you are using Garmin Express, then you can access the app settings by selecting the 3 dots next to the app. You will have 36 possible API calls that you can enter.</p>
<figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;yS4VBx2LyATq.webp" alt="ApiCall settings page"  />
    </div>
    
    <figcaption><p>Yes that formatting is atrocious but it works at least!</p>
</figcaption>
    
</figure>
<blockquote>
<p>webhooks</p>
</blockquote>
<pre class="giallo z-code"><code data-lang="typescript"><span class="giallo-l"><span class="z-punctuation">{</span><span class="z-source">deviceName</span><span class="z-punctuation">:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Broadcast&quot;</span><span class="z-punctuation">,</span><span class="z-source">actionName</span><span class="z-punctuation">:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Chores&quot;</span><span class="z-punctuation">,</span><span class="z-source">url</span><span class="z-punctuation">:</span><span class="z-punctuation z-definition z-string z-string"> &quot;http://192.168.40.21:8123/api/webhook/Aere&quot;</span><span class="z-punctuation">,</span><span class="z-source">method</span><span class="z-punctuation">:</span><span class="z-punctuation z-definition z-string z-string"> &quot;GET&quot;</span><span class="z-punctuation">,</span><span class="z-source">headers</span><span class="z-punctuation">:</span><span class="z-punctuation z-definition z-string z-string">&quot;{&quot;</span><span class="z-variable z-other z-readwrite">Content</span><span class="z-keyword z-operator">-</span><span class="z-variable z-other z-readwrite">Type</span><span class="z-punctuation z-definition z-string z-string">&quot;:&quot;</span><span class="z-variable z-other z-readwrite">application</span><span class="z-keyword z-operator">/</span><span class="z-variable z-other z-readwrite">x</span><span class="z-keyword z-operator">-</span><span class="z-variable z-other z-readwrite">www</span><span class="z-keyword z-operator">-</span><span class="z-variable z-other z-readwrite">form</span><span class="z-keyword z-operator">-</span><span class="z-variable z-other z-readwrite">urlencoded</span><span class="z-punctuation z-definition z-string z-string">&quot;}&quot;</span><span class="z-punctuation">}</span></span>
<span class="giallo-l"><span class="z-punctuation">{</span><span class="z-source">deviceName</span><span class="z-punctuation">:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Spotify&quot;</span><span class="z-punctuation">,</span><span class="z-source">actionName</span><span class="z-punctuation">:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Discover Weekly&quot;</span><span class="z-punctuation">,</span><span class="z-source">url</span><span class="z-punctuation">:</span><span class="z-punctuation z-definition z-string z-string"> &quot;http://192.168.40.21:8123/api/webhook/-djNd5aMidD6Q3w2jgYDu50ix&quot;</span><span class="z-punctuation">,</span><span class="z-source">method</span><span class="z-punctuation">:</span><span class="z-punctuation z-definition z-string z-string"> &quot;GET&quot;</span><span class="z-punctuation">,</span><span class="z-source">headers</span><span class="z-punctuation">:</span><span class="z-punctuation z-definition z-string z-string">&quot;{&quot;</span><span class="z-variable z-other z-readwrite">Content</span><span class="z-keyword z-operator">-</span><span class="z-variable z-other z-readwrite">Type</span><span class="z-punctuation z-definition z-string z-string">&quot;:&quot;</span><span class="z-variable z-other z-readwrite">application</span><span class="z-keyword z-operator">/</span><span class="z-variable z-other z-readwrite">x</span><span class="z-keyword z-operator">-</span><span class="z-variable z-other z-readwrite">www</span><span class="z-keyword z-operator">-</span><span class="z-variable z-other z-readwrite">form</span><span class="z-keyword z-operator">-</span><span class="z-variable z-other z-readwrite">urlencoded</span><span class="z-punctuation z-definition z-string z-string">&quot;}&quot;</span><span class="z-punctuation">,</span><span class="z-source">actionIcon</span><span class="z-punctuation">:</span><span class="z-constant z-numeric">40</span><span class="z-punctuation">}</span></span>
<span class="giallo-l"><span class="z-punctuation">{</span><span class="z-source">deviceName</span><span class="z-punctuation">:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Spotify&quot;</span><span class="z-punctuation">,</span><span class="z-source">actionName</span><span class="z-punctuation">:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Liked Songs&quot;</span><span class="z-punctuation">,</span><span class="z-source">url</span><span class="z-punctuation">:</span><span class="z-punctuation z-definition z-string z-string"> &quot;http://192.168.40.21:8123/api/webhook/liked-songs-6TrVEY-TzVsAeFX8Mt8FUpJN&quot;</span><span class="z-punctuation">,</span><span class="z-source">method</span><span class="z-punctuation">:</span><span class="z-punctuation z-definition z-string z-string"> &quot;GET&quot;</span><span class="z-punctuation">,</span><span class="z-source">headers</span><span class="z-punctuation">:</span><span class="z-punctuation z-definition z-string z-string">&quot;{&quot;</span><span class="z-variable z-other z-readwrite">Content</span><span class="z-keyword z-operator">-</span><span class="z-variable z-other z-readwrite">Type</span><span class="z-punctuation z-definition z-string z-string">&quot;:&quot;</span><span class="z-variable z-other z-readwrite">application</span><span class="z-keyword z-operator">/</span><span class="z-variable z-other z-readwrite">x</span><span class="z-keyword z-operator">-</span><span class="z-variable z-other z-readwrite">www</span><span class="z-keyword z-operator">-</span><span class="z-variable z-other z-readwrite">form</span><span class="z-keyword z-operator">-</span><span class="z-variable z-other z-readwrite">urlencoded</span><span class="z-punctuation z-definition z-string z-string">&quot;}&quot;</span><span class="z-punctuation">,</span><span class="z-source">actionIcon</span><span class="z-punctuation">:</span><span class="z-constant z-numeric">40</span><span class="z-punctuation">}</span></span>
<span class="giallo-l"><span class="z-punctuation">{</span><span class="z-source">deviceName</span><span class="z-punctuation">:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Spotify&quot;</span><span class="z-punctuation">,</span><span class="z-source">actionName</span><span class="z-punctuation">:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Sleep Songs&quot;</span><span class="z-punctuation">,</span><span class="z-source">url</span><span class="z-punctuation">:</span><span class="z-punctuation z-definition z-string z-string"> &quot;http://192.168.40.21:8123/api/webhook/sleep-songs-jA1nrTpc9PuKumvzNDFteBDK&quot;</span><span class="z-punctuation">,</span><span class="z-source">method</span><span class="z-punctuation">:</span><span class="z-punctuation z-definition z-string z-string"> &quot;GET&quot;</span><span class="z-punctuation">,</span><span class="z-source">headers</span><span class="z-punctuation">:</span><span class="z-punctuation z-definition z-string z-string">&quot;{&quot;</span><span class="z-variable z-other z-readwrite">Content</span><span class="z-keyword z-operator">-</span><span class="z-variable z-other z-readwrite">Type</span><span class="z-punctuation z-definition z-string z-string">&quot;:&quot;</span><span class="z-variable z-other z-readwrite">application</span><span class="z-keyword z-operator">/</span><span class="z-variable z-other z-readwrite">x</span><span class="z-keyword z-operator">-</span><span class="z-variable z-other z-readwrite">www</span><span class="z-keyword z-operator">-</span><span class="z-variable z-other z-readwrite">form</span><span class="z-keyword z-operator">-</span><span class="z-variable z-other z-readwrite">urlencoded</span><span class="z-punctuation z-definition z-string z-string">&quot;}&quot;</span><span class="z-punctuation">,</span><span class="z-source">actionIcon</span><span class="z-punctuation">:</span><span class="z-constant z-numeric">51</span><span class="z-punctuation">}</span></span>
<span class="giallo-l"><span class="z-punctuation">{</span><span class="z-source">deviceName</span><span class="z-punctuation">:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Media Controls&quot;</span><span class="z-punctuation">,</span><span class="z-source">actionName</span><span class="z-punctuation">:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Play/Pause&quot;</span><span class="z-punctuation">,</span><span class="z-source">url</span><span class="z-punctuation">:</span><span class="z-punctuation z-definition z-string z-string"> &quot;http://192.168.40.21:8123/api/webhook/playpause-DTNDt-RzOqgGTggOnV_sXMLm&quot;</span><span class="z-punctuation">,</span><span class="z-source">method</span><span class="z-punctuation">:</span><span class="z-punctuation z-definition z-string z-string"> &quot;GET&quot;</span><span class="z-punctuation">,</span><span class="z-source">headers</span><span class="z-punctuation">:</span><span class="z-punctuation z-definition z-string z-string">&quot;{&quot;</span><span class="z-variable z-other z-readwrite">Content</span><span class="z-keyword z-operator">-</span><span class="z-variable z-other z-readwrite">Type</span><span class="z-punctuation z-definition z-string z-string">&quot;:&quot;</span><span class="z-variable z-other z-readwrite">application</span><span class="z-keyword z-operator">/</span><span class="z-variable z-other z-readwrite">x</span><span class="z-keyword z-operator">-</span><span class="z-variable z-other z-readwrite">www</span><span class="z-keyword z-operator">-</span><span class="z-variable z-other z-readwrite">form</span><span class="z-keyword z-operator">-</span><span class="z-variable z-other z-readwrite">urlencoded</span><span class="z-punctuation z-definition z-string z-string">&quot;}&quot;</span><span class="z-punctuation">,</span><span class="z-source">actionIcon</span><span class="z-punctuation">:</span><span class="z-constant z-numeric">43</span><span class="z-punctuation">}</span></span>
<span class="giallo-l"><span class="z-punctuation">{</span><span class="z-source">deviceName</span><span class="z-punctuation">:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Media Controls&quot;</span><span class="z-punctuation">,</span><span class="z-source">actionName</span><span class="z-punctuation">:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Next&quot;</span><span class="z-punctuation">,</span><span class="z-source">url</span><span class="z-punctuation">:</span><span class="z-punctuation z-definition z-string z-string"> &quot;http://192.168.40.21:8123/api/webhook/skip-forward-IvQkjhn2oev7VY0mb_xZDDCK&quot;</span><span class="z-punctuation">,</span><span class="z-source">method</span><span class="z-punctuation">:</span><span class="z-punctuation z-definition z-string z-string"> &quot;GET&quot;</span><span class="z-punctuation">,</span><span class="z-source">headers</span><span class="z-punctuation">:</span><span class="z-punctuation z-definition z-string z-string">&quot;{&quot;</span><span class="z-variable z-other z-readwrite">Content</span><span class="z-keyword z-operator">-</span><span class="z-variable z-other z-readwrite">Type</span><span class="z-punctuation z-definition z-string z-string">&quot;:&quot;</span><span class="z-variable z-other z-readwrite">application</span><span class="z-keyword z-operator">/</span><span class="z-variable z-other z-readwrite">x</span><span class="z-keyword z-operator">-</span><span class="z-variable z-other z-readwrite">www</span><span class="z-keyword z-operator">-</span><span class="z-variable z-other z-readwrite">form</span><span class="z-keyword z-operator">-</span><span class="z-variable z-other z-readwrite">urlencoded</span><span class="z-punctuation z-definition z-string z-string">&quot;}&quot;</span><span class="z-punctuation">,</span><span class="z-source">actionIcon</span><span class="z-punctuation">:</span><span class="z-constant z-numeric">41</span><span class="z-punctuation">}</span></span>
<span class="giallo-l"><span class="z-punctuation">{</span><span class="z-source">deviceName</span><span class="z-punctuation">:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Media Controls&quot;</span><span class="z-punctuation">,</span><span class="z-source">actionName</span><span class="z-punctuation">:</span><span class="z-punctuation z-definition z-string z-string"> &quot;Previous&quot;</span><span class="z-punctuation">,</span><span class="z-source">url</span><span class="z-punctuation">:</span><span class="z-punctuation z-definition z-string z-string"> &quot;http://192.168.40.21:8123/api/webhook/skip-backwards-A9byoXP-QwSv_aoQ2FtX-_Qx&quot;</span><span class="z-punctuation">,</span><span class="z-source">method</span><span class="z-punctuation">:</span><span class="z-punctuation z-definition z-string z-string"> &quot;GET&quot;</span><span class="z-punctuation">,</span><span class="z-source">headers</span><span class="z-punctuation">:</span><span class="z-punctuation z-definition z-string z-string">&quot;{&quot;</span><span class="z-variable z-other z-readwrite">Content</span><span class="z-keyword z-operator">-</span><span class="z-variable z-other z-readwrite">Type</span><span class="z-punctuation z-definition z-string z-string">&quot;:&quot;</span><span class="z-variable z-other z-readwrite">application</span><span class="z-keyword z-operator">/</span><span class="z-variable z-other z-readwrite">x</span><span class="z-keyword z-operator">-</span><span class="z-variable z-other z-readwrite">www</span><span class="z-keyword z-operator">-</span><span class="z-variable z-other z-readwrite">form</span><span class="z-keyword z-operator">-</span><span class="z-variable z-other z-readwrite">urlencoded</span><span class="z-punctuation z-definition z-string z-string">&quot;}&quot;</span><span class="z-punctuation">,</span><span class="z-source">actionIcon</span><span class="z-punctuation">:</span><span class="z-constant z-numeric">42</span><span class="z-punctuation">}</span></span></code></pre>
<p>These are the actions that I configured for my watch so far. To customize for your API calls you need to change the <code>deviceName</code>, <code>actionName</code>, and <code>url</code> fields. The <code>method</code> and <code>headers</code> need to stay the same across all actions. If you want to add an icon to that action, then you can configure that with the <code>actionIcon</code> field. A table with the possible icons is included below, sourced from APICall’s <a rel="noopener external" target="_blank" href="https://apicall.dumesnil.net/documentation_en.html">documentation</a>.</p>
<figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;c1U_5651OqAH.webp" alt="ApiCall icons"  />
    </div>
    
</figure>
<p>In conclusion, you can use APICall to trigger actions in home assistant from your Garmin smartwatch. I hope this tutorial proved to be useful, and have a great rest of your day (or night).</p>
<ul>
<li>Written on <code>2023-08-04</code> and republished to this blog on <code>2024-10-31</code></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Install TrueNAS Core on Proxmox</title>
      <link>https://dunkirk.sh/blog/install-truenas-core-proxmox/</link>
      <guid isPermaLink="true">https://dunkirk.sh/blog/install-truenas-core-proxmox/</guid>
      <pubDate>Mon, 10 Jul 2023 00:00:00 +0000</pubDate>
      
      
      <dc:creator>Kieran Klukas</dc:creator>
      
      
        
      <category>tutorial</category>
        
      <category>archival</category>
        
      
      <description>
        
          &amp;lt;p&amp;gt;I have been using Proxmox for a while now but I’ve also wanted to make use of some large HDDs that have been lying around. I really didn’t want to get another machine just for TrueNAS so I decided to install it on Proxmox. This is how I did it.&amp;lt;&#x2F;p&amp;gt;
        
      </description>
      <content:encoded><![CDATA[<p>I have been using Proxmox for a while now but I’ve also wanted to make use of some large HDDs that have been lying around. I really didn’t want to get another machine just for TrueNAS so I decided to install it on Proxmox. This is how I did it.</p>
<span id="continue-reading"></span><figure class="8TB" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;t-S80BbH3_js.webp" alt="screenshot of the vault vm in proxmox"  />
    </div>
    
    <figcaption><p>my active vault storing 1.8TB of old projects</p>
</figcaption>
    
</figure>
<h2 id="introduction">Introduction</h2>
<div class="callout callout-gray">
  <div class="callout-title">
    <span class="callout-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20h9"></path><path d="M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4Z"></path></svg></span>
    <strong>Note</strong>
  </div>
  <div class="callout-content">
    <p>I have since found out that running TrueNAS in a VM passing through the drives may not be a very good solution as it essentially just creates a large virtual disk that is the size of the drive you are passing through. Because of this I will not be using this setup in my homelab and will instead create a large ZFS pool on Proxmox. However if you are fine with those downsides, then have fun and enjoy the tutorial.</p>

  </div>
</div>
<p>To install <a rel="noopener external" target="_blank" href="https://www.truenas.com/download-truenas-core/#">TrueNAS Core</a> on <a rel="noopener external" target="_blank" href="https://www.proxmox.com/en/proxmox-ve">Proxmox</a> you need three things:</p>
<ol>
<li>A copy of <a rel="noopener external" target="_blank" href="https://www.proxmox.com/en/proxmox-ve">Proxmox</a> — A complete, open-source server management platform for enterprise virtualization.</li>
<li>A <a rel="noopener external" target="_blank" href="https://www.truenas.com/download-truenas-core/#">TrueNAS CORE</a> ISO — World’s #1 NAS Operating System</li>
<li>HDDs — You can use whatever you want, but I will be using three Barracuda ES 750GB drives</li>
</ol>
<h2 id="install-truenas-core">Install TrueNAS Core</h2>
<p>Sign-in to Proxmox and upload your ISO to the local storage or, download the file directly from the link using the built-in ISO fetcher.</p>
<figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;-erDtuONgTjb.webp" alt="download iso tool in proxmox"  />
    </div>
    
</figure>
<p>Next to create the VM, the only thing that needs to be changed from the defaults is the memory, which I set to <code>8192 MB</code> (8 GB).</p>
<figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;xXwvumZox22j.webp" alt="create a vm modal in proxmox"  />
    </div>
    
</figure>
<p>Now finish creating the VM and click on the VM after it is created. Go to options and enable start at boot.</p>
<figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;dWrsydIm5wfF.webp" alt="start at boot checkbox"  />
    </div>
    
</figure>
<p>Next, we need to pass through the physical drives to the VM. Open a terminal on the Proxmox server (use the built-in terminal or ssh in) and run the following command. Only run the part after the #.</p>
<blockquote>
<p>root@thespia:~# lsblk -o +MODEL,SERIAL</p>
</blockquote>
<pre class="giallo z-code"><code data-lang="shellscript"><span class="giallo-l"><span class="z-entity z-name z-function">NAME</span><span class="z-string">                           MAJ:MIN RM   SIZE RO TYPE MOUNTPOINT MODEL                   SERIAL</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">sda</span><span class="z-string">                              8:0</span><span class="z-constant z-numeric">    0</span><span class="z-string"> 698.6G</span><span class="z-constant z-numeric">  0</span><span class="z-string"> disk            ST3750330NS             9QK2GT8R</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">sdb</span><span class="z-string">                              8:16</span><span class="z-constant z-numeric">   0</span><span class="z-string"> 698.6G</span><span class="z-constant z-numeric">  0</span><span class="z-string"> disk            ST3750640NS             3QD0AYE0</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">sdc</span><span class="z-string">                              8:32</span><span class="z-constant z-numeric">   0</span><span class="z-string"> 698.6G</span><span class="z-constant z-numeric">  0</span><span class="z-string"> disk            ST3750640NS             3QD0BQ5G</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">sdd</span><span class="z-string">                              8:48</span><span class="z-constant z-numeric">   1</span><span class="z-string"> 111.8G</span><span class="z-constant z-numeric">  0</span><span class="z-string"> disk            Hitachi_HTS543212L9SA02 090130FBEB00LGGJ35RF</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">├─sdd1</span><span class="z-string">                           8:49</span><span class="z-constant z-numeric">   1</span><span class="z-string">  1007K</span><span class="z-constant z-numeric">  0</span><span class="z-string"> part</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">├─sdd2</span><span class="z-string">                           8:50</span><span class="z-constant z-numeric">   1</span><span class="z-string">   512M</span><span class="z-constant z-numeric">  0</span><span class="z-string"> part /boot/efi</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">└─sdd3</span><span class="z-string">                           8:51</span><span class="z-constant z-numeric">   1</span><span class="z-string"> 111.3G</span><span class="z-constant z-numeric">  0</span><span class="z-string"> part</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">    ├─pve-swap</span><span class="z-string">                   253:0</span><span class="z-constant z-numeric">    0</span><span class="z-string">     8G</span><span class="z-constant z-numeric">  0</span><span class="z-string"> lvm</span><span class="z-source">  [SWAP]</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">    ├─pve-root</span><span class="z-string">                   253:1</span><span class="z-constant z-numeric">    0</span><span class="z-string">  37.8G</span><span class="z-constant z-numeric">  0</span><span class="z-string"> lvm  /</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">    ├─pve-data_tmeta</span><span class="z-string">             253:2</span><span class="z-constant z-numeric">    0</span><span class="z-string">     1G</span><span class="z-constant z-numeric">  0</span><span class="z-string"> lvm</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">    │</span><span class="z-string"> └─pve-data-tpool           253:4</span><span class="z-constant z-numeric">    0</span><span class="z-string">  49.6G</span><span class="z-constant z-numeric">  0</span><span class="z-string"> lvm</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">    │</span><span class="z-string">   ├─pve-data               253:5</span><span class="z-constant z-numeric">    0</span><span class="z-string">  49.6G</span><span class="z-constant z-numeric">  1</span><span class="z-string"> lvm</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">    │</span><span class="z-string">   ├─pve-vm--100--cloudinit 253:6</span><span class="z-constant z-numeric">    0</span><span class="z-string">     4M</span><span class="z-constant z-numeric">  0</span><span class="z-string"> lvm</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">    │</span><span class="z-string">   └─pve-vm--101--cloudinit 253:7</span><span class="z-constant z-numeric">    0</span><span class="z-string">     4M</span><span class="z-constant z-numeric">  0</span><span class="z-string"> lvm</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">    └─pve-data_tdata</span><span class="z-string">             253:3</span><span class="z-constant z-numeric">    0</span><span class="z-string">  49.6G</span><span class="z-constant z-numeric">  0</span><span class="z-string"> lvm</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">    └─pve-data-tpool</span><span class="z-string">           253:4</span><span class="z-constant z-numeric">    0</span><span class="z-string">  49.6G</span><span class="z-constant z-numeric">  0</span><span class="z-string"> lvm</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">        ├─pve-data</span><span class="z-string">               253:5</span><span class="z-constant z-numeric">    0</span><span class="z-string">  49.6G</span><span class="z-constant z-numeric">  1</span><span class="z-string"> lvm</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">        ├─pve-vm--100--cloudinit</span><span class="z-string"> 253:6</span><span class="z-constant z-numeric">    0</span><span class="z-string">     4M</span><span class="z-constant z-numeric">  0</span><span class="z-string"> lvm</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">        └─pve-vm--101--cloudinit</span><span class="z-string"> 253:7</span><span class="z-constant z-numeric">    0</span><span class="z-string">     4M</span><span class="z-constant z-numeric">  0</span><span class="z-string"> lvm</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">sde</span><span class="z-string">                              8:64</span><span class="z-constant z-numeric">   0</span><span class="z-string"> 465.8G</span><span class="z-constant z-numeric">  0</span><span class="z-string"> disk            WDC_WD5000AAKS-65YGA0   WD-WCAS83511331</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">├─sde1</span><span class="z-string">                           8:65</span><span class="z-constant z-numeric">   0</span><span class="z-string"> 465.8G</span><span class="z-constant z-numeric">  0</span><span class="z-string"> part</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">└─sde9</span><span class="z-string">                           8:73</span><span class="z-constant z-numeric">   0</span><span class="z-string">     8M</span><span class="z-constant z-numeric">  0</span><span class="z-string"> part</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">zd0</span><span class="z-string">                            230:0</span><span class="z-constant z-numeric">    0</span><span class="z-string">    32G</span><span class="z-constant z-numeric">  0</span><span class="z-string"> disk</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">├─zd0p1</span><span class="z-string">                        230:1</span><span class="z-constant z-numeric">    0</span><span class="z-string">   100M</span><span class="z-constant z-numeric">  0</span><span class="z-string"> part</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">├─zd0p2</span><span class="z-string">                        230:2</span><span class="z-constant z-numeric">    0</span><span class="z-string">    16M</span><span class="z-constant z-numeric">  0</span><span class="z-string"> part</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">├─zd0p3</span><span class="z-string">                        230:3</span><span class="z-constant z-numeric">    0</span><span class="z-string">  31.4G</span><span class="z-constant z-numeric">  0</span><span class="z-string"> part</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">└─zd0p4</span><span class="z-string">                        230:4</span><span class="z-constant z-numeric">    0</span><span class="z-string">   522M</span><span class="z-constant z-numeric">  0</span><span class="z-string"> part</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">zd16</span><span class="z-string">                           230:16</span><span class="z-constant z-numeric">   0</span><span class="z-string">    80G</span><span class="z-constant z-numeric">  0</span><span class="z-string"> disk</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">├─zd16p1</span><span class="z-string">                       230:17</span><span class="z-constant z-numeric">   0</span><span class="z-string">     1M</span><span class="z-constant z-numeric">  0</span><span class="z-string"> part</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">└─zd16p2</span><span class="z-string">                       230:18</span><span class="z-constant z-numeric">   0</span><span class="z-string">    80G</span><span class="z-constant z-numeric">  0</span><span class="z-string"> part</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">zd32</span><span class="z-string">                           230:32</span><span class="z-constant z-numeric">   0</span><span class="z-string">     4M</span><span class="z-constant z-numeric">  0</span><span class="z-string"> disk</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">zd48</span><span class="z-string">                           230:48</span><span class="z-constant z-numeric">   0</span><span class="z-string">    80G</span><span class="z-constant z-numeric">  0</span><span class="z-string"> disk</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">├─zd48p1</span><span class="z-string">                       230:49</span><span class="z-constant z-numeric">   0</span><span class="z-string">     1M</span><span class="z-constant z-numeric">  0</span><span class="z-string"> part</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">└─zd48p2</span><span class="z-string">                       230:50</span><span class="z-constant z-numeric">   0</span><span class="z-string">    80G</span><span class="z-constant z-numeric">  0</span><span class="z-string"> part</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">zd64</span><span class="z-string">                           230:64</span><span class="z-constant z-numeric">   0</span><span class="z-string">     1M</span><span class="z-constant z-numeric">  0</span><span class="z-string"> disk</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">zd80</span><span class="z-string">                           230:80</span><span class="z-constant z-numeric">   0</span><span class="z-string">    32G</span><span class="z-constant z-numeric">  0</span><span class="z-string"> disk</span></span></code></pre>
<p>In my server <code>sda, sdb, and sdc</code> are my drives. I can tell because they have no partitions and are <code>698.6G</code>. Next, based on the serial numbers of the disks, find the <code>dev/disk/by-id</code> of the drive.</p>
<blockquote>
<p>root@thespia:~# ls /dev/disk/by-id/</p>
</blockquote>
<pre class="giallo z-code"><code data-lang="shellscript"><span class="giallo-l"><span class="z-entity z-name z-function">ata-Hitachi_HTS543212L9SA02_090130FBEB00LGGJ35RF</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">ata-Hitachi_HTS543212L9SA02_090130FBEB00LGGJ35RF-part1</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">ata-Hitachi_HTS543212L9SA02_090130FBEB00LGGJ35RF-part2</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">ata-Hitachi_HTS543212L9SA02_090130FBEB00LGGJ35RF-part3</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">ata-ST3750330NS_9QK2GT8R</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">ata-ST3750640NS_3QD0AYE0</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">ata-ST3750640NS_3QD0BQ5G</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">ata-WDC_WD5000AAKS-65YGA0_WD-WCAS83511331</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">ata-WDC_WD5000AAKS-65YGA0_WD-WCAS83511331-part1</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">ata-WDC_WD5000AAKS-65YGA0_WD-WCAS83511331-part9</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">dm-name-pve-root</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">dm-name-pve-swap</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">dm-name-pve-vm--100--cloudinit</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">dm-name-pve-vm--101--cloudinit</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">dm-uuid-LVM-i2jw2DEc8aJxdhf3mg7sAcAbc57lfeNL967xBhsO2KsTDqSJ5KB9pGqef5HjQJHk</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">dm-uuid-LVM-i2jw2DEc8aJxdhf3mg7sAcAbc57lfeNLQ6hkWGll1H38yFz0ty3RmmJPSRSbj1sa</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">dm-uuid-LVM-i2jw2DEc8aJxdhf3mg7sAcAbc57lfeNLrSofGgZtL41un6baoCpRHunOrbJeMTeO</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">dm-uuid-LVM-i2jw2DEc8aJxdhf3mg7sAcAbc57lfeNLWPjyk8d4ik2D6KIcp2zaugdFsHB4TNOM</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">lvm-pv-uuid-pRECVX-zqKA-evrD-PNof-sTYg-zNrD-WUelFe</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">usb-Generic-_Multi-Card_20120926571200000-0:0</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">wwn-0x5000c50015a53388</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">wwn-0x5000cca562c751e4</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">wwn-0x5000cca562c751e4-part1</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">wwn-0x5000cca562c751e4-part2</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">wwn-0x5000cca562c751e4-part3</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">wwn-0x50014ee2ab77b23f</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">wwn-0x50014ee2ab77b23f-part1</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">wwn-0x50014ee2ab77b23f-part9</span></span></code></pre>
<p>In my case, the ID of the drives I want are <code>ata-ST3750330NS_9QK2GT8</code>, <code>ata-ST3750640NS_3QD0AYE0</code>, and <code>ata-ST3750640NS_3QD0BQ5G</code>.</p>
<p>Now find your VM_ID, mine is 102.</p>
<figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;kDlPW3DcBeJM.webp" alt="vm list in proxmox"  />
    </div>
    
</figure>
<p>Run the following command, replacing the VM_ID and DISK_ID with yours.</p>
<blockquote>
<p>root@thespia:~# qm set VM_ID -scsi1 /dev/disk/by-id/DISK_ID</p>
</blockquote>
<pre class="giallo z-code"><code data-lang="shellscript"><span class="giallo-l"><span class="z-entity z-name z-function">root@thespia:~#</span><span class="z-string"> qm set</span><span class="z-constant z-numeric"> 102</span><span class="z-string"> -scsi1 /dev/disk/by-id/ata-ST3750330NS_9QK2GT8R</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">update</span><span class="z-string"> VM 102: -scsi1 /dev/disk/by-id/ata-ST3750330NS_9QK2GT8R</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">root@thespia:~#</span><span class="z-string"> qm set</span><span class="z-constant z-numeric"> 102</span><span class="z-string"> -scsi2 /dev/disk/by-id/ata-ST3750640NS_3QD0AYE0</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">update</span><span class="z-string"> VM 102: -scsi2 /dev/disk/by-id/ata-ST3750640NS_3QD0AYE0</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">root@thespia:~#</span><span class="z-string"> qm set</span><span class="z-constant z-numeric"> 102</span><span class="z-string"> -scsi3 /dev/disk/by-id/ata-ST3750640NS_3QD0BQ5G</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function">update</span><span class="z-string"> VM 102: -scsi3 /dev/disk/by-id/ata-ST3750640NS_3QD0BQ5G</span></span></code></pre>
<p>Here is how it appears in Proxmox:</p>
<figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;1fUVr0Jk7wBg.webp" alt="hardware page of the vm in proxmox"  />
    </div>
    
</figure>
<p>If everything went well, then you can start your VM now. After it finishes booting up, you will get the screen below. Make sure Install/Upgrade is selected and hit enter.</p>
<figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;kGuQCi-UJ-XU.webp" alt="truenas startup screen"  />
    </div>
    
</figure>
<p>You will then get this screen, use space to select the first drive and hit enter.</p>
<figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;Z4v0J-gL1Jn9.webp" alt="destination media screen"  />
    </div>
    
</figure>
<p>Hit enter one last time and enter your password.</p>
<p><figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;7Dok7mhDl-44.webp" alt="confirm erase page"  />
    </div>
    
</figure>

<figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;iV97jX_E_lSM.webp" alt="repeat password page"  />
    </div>
    
</figure>
</p>
<p>Select BIOS, as this is the default mode for Proxmox VMs.</p>
<figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;TJJEYiblQh5c.webp" alt="boot via bios or via uefi screen"  />
    </div>
    
</figure>
<p>After about five to ten minutes, the installation process will finish and the VM will ask you to remove installation media and reboot.</p>
<p><figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;9SAGd4cObCee.webp" alt="installation succeded message"  />
    </div>
    
</figure>

<figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;cub6hDGGZuMB.webp" alt="hardware screen in proxmox"  />
    </div>
    
</figure>
</p>
<p>Select the installation media and remove it with the top button, go back to the console and hit enter, which will take you back to the main menu. On the main menu, select reboot with the arrow keys and hit enter.</p>
<figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;5VpCwlveDO8d.webp" alt="power options screen in truenas"  />
    </div>
    
</figure>
<p>Once the machine restarts, it will display an IP address in the console.</p>
<figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;QZcl0rYVg8Hu.webp" alt="ip address displayed in proxmox console"  />
    </div>
    
</figure>
<p>Upon connecting to the IP address, you will get this screen. Use the root username and the password, previously configured, to login.</p>
<figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;SUBmMtGZhoNK.webp" alt="truenas web ui signin page"  />
    </div>
    
</figure>
<p>Once logged in, I updated the system using the button on the home screen.</p>
<figure class="center" >
    <div class="img-container" data-lightbox>
        <img src="https:&#x2F;&#x2F;l4.dunkirk.sh&#x2F;i&#x2F;4sumumhGS0h0.webp" alt="check for updates button in the truenas web ui"  />
    </div>
    
</figure>
<p>I chose not to save the configuration file when prompted, proceeded to install the updates, and rebooted.</p>
<p>I hope you enjoyed the tutorial! My inspiration to make this came from watching <a rel="noopener external" target="_blank" href="https://www.youtube.com/watch?v=M3pKprTdNqQ">“How to run TrueNAS on Proxmox?”</a> by <a rel="noopener external" target="_blank" href="https://www.youtube.com/@christianlempa">Christian Lempa</a>. I encourage you to watch his video if you want a video guide to installing TrueNAS on Proxmox.</p>
<ul>
<li>Written on <code>2023-07-10</code> and republished to this blog (with minor edits) on <code>2024-10-31</code></li>
</ul>
]]></content:encoded>
    </item>
    
  </channel>
</rss>
