{"id":85151,"date":"2026-05-05T16:13:25","date_gmt":"2026-05-05T23:13:25","guid":{"rendered":"https:\/\/www.redfin.com\/news\/?page_id=85151"},"modified":"2026-05-07T09:56:59","modified_gmt":"2026-05-07T16:56:59","slug":"downloads","status":"publish","type":"page","link":"https:\/\/www.redfin.com\/news\/data-center\/downloads\/","title":{"rendered":"Downloads"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"85151\" class=\"elementor elementor-85151\" data-elementor-post-type=\"page\">\n\t\t\t\t<div class=\"elementor-element elementor-element-5c87bfd e-flex e-con-boxed e-con e-parent\" data-id=\"5c87bfd\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-2455eee elementor-widget elementor-widget-template\" data-id=\"2455eee\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"template.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"elementor-template\">\n\t\t\t\t\t<div data-elementor-type=\"container\" data-elementor-id=\"85190\" class=\"elementor elementor-85190\" data-elementor-post-type=\"elementor_library\">\n\t\t\t\t<div class=\"elementor-element elementor-element-98128b4 e-flex e-con-boxed e-con e-parent\" data-id=\"98128b4\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t<div class=\"elementor-element elementor-element-2e49d5d2 e-con-full e-flex e-con e-child\" data-id=\"2e49d5d2\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-96aef1b e-fit_to_content e-n-menu-none dc-mega-menu e-n-menu-layout-horizontal elementor-widget elementor-widget-n-menu\" data-id=\"96aef1b\" data-element_type=\"widget\" data-e-type=\"widget\" data-settings=\"{&quot;menu_items&quot;:[{&quot;item_title&quot;:&quot;Data Center&quot;,&quot;item_dropdown_content&quot;:&quot;&quot;,&quot;item_icon_active&quot;:null,&quot;item_link&quot;:{&quot;url&quot;:&quot;https:\\\/\\\/redfin.com\\\/news\\\/data-center\\\/&quot;,&quot;is_external&quot;:&quot;&quot;,&quot;nofollow&quot;:&quot;&quot;,&quot;custom_attributes&quot;:&quot;&quot;},&quot;_id&quot;:&quot;1972fcf&quot;,&quot;item_icon&quot;:{&quot;value&quot;:&quot;&quot;,&quot;library&quot;:&quot;&quot;},&quot;element_id&quot;:&quot;&quot;},{&quot;item_title&quot;:&quot;Dashboards&quot;,&quot;item_dropdown_content&quot;:&quot;yes&quot;,&quot;item_icon_active&quot;:null,&quot;_id&quot;:&quot;48cf2c5&quot;,&quot;item_link&quot;:{&quot;url&quot;:&quot;&quot;,&quot;is_external&quot;:&quot;&quot;,&quot;nofollow&quot;:&quot;&quot;,&quot;custom_attributes&quot;:&quot;&quot;},&quot;item_icon&quot;:{&quot;value&quot;:&quot;&quot;,&quot;library&quot;:&quot;&quot;},&quot;element_id&quot;:&quot;&quot;},{&quot;item_title&quot;:&quot;Downloads&quot;,&quot;item_dropdown_content&quot;:&quot;&quot;,&quot;item_icon_active&quot;:null,&quot;item_link&quot;:{&quot;url&quot;:&quot;https:\\\/\\\/redfin.com\\\/news\\\/data-center\\\/downloads\\\/&quot;,&quot;is_external&quot;:&quot;&quot;,&quot;nofollow&quot;:&quot;&quot;,&quot;custom_attributes&quot;:&quot;&quot;},&quot;_id&quot;:&quot;2dbf668&quot;,&quot;item_icon&quot;:{&quot;value&quot;:&quot;&quot;,&quot;library&quot;:&quot;&quot;},&quot;element_id&quot;:&quot;&quot;},{&quot;item_title&quot;:&quot;Methodology&quot;,&quot;item_dropdown_content&quot;:&quot;&quot;,&quot;item_icon_active&quot;:null,&quot;item_link&quot;:{&quot;url&quot;:&quot;https:\\\/\\\/redfin.com\\\/news\\\/data-center\\\/methodology\\\/&quot;,&quot;is_external&quot;:&quot;&quot;,&quot;nofollow&quot;:&quot;&quot;,&quot;custom_attributes&quot;:&quot;&quot;},&quot;_id&quot;:&quot;9d4e024&quot;,&quot;item_icon&quot;:{&quot;value&quot;:&quot;&quot;,&quot;library&quot;:&quot;&quot;},&quot;element_id&quot;:&quot;&quot;}],&quot;content_width&quot;:&quot;fit_to_content&quot;,&quot;content_horizontal_position&quot;:&quot;left&quot;,&quot;item_position_horizontal&quot;:&quot;center&quot;,&quot;breakpoint_selector&quot;:&quot;none&quot;,&quot;item_layout&quot;:&quot;horizontal&quot;,&quot;open_on&quot;:&quot;hover&quot;,&quot;horizontal_scroll&quot;:&quot;disable&quot;,&quot;menu_item_title_distance_from_content&quot;:{&quot;unit&quot;:&quot;px&quot;,&quot;size&quot;:0,&quot;sizes&quot;:[]},&quot;menu_item_title_distance_from_content_tablet&quot;:{&quot;unit&quot;:&quot;px&quot;,&quot;size&quot;:&quot;&quot;,&quot;sizes&quot;:[]},&quot;menu_item_title_distance_from_content_mobile&quot;:{&quot;unit&quot;:&quot;px&quot;,&quot;size&quot;:&quot;&quot;,&quot;sizes&quot;:[]}}\" data-widget_type=\"mega-menu.default\">\n\t\t\t\t\t\t\t<nav class=\"e-n-menu\" data-widget-number=\"158\" aria-label=\"Menu\">\n\t\t\t\t\t<button class=\"e-n-menu-toggle\" id=\"menu-toggle-158\" aria-haspopup=\"true\" aria-expanded=\"false\" aria-controls=\"menubar-158\" aria-label=\"Menu Toggle\">\n\t\t\t<span class=\"e-n-menu-toggle-icon e-open\">\n\t\t\t\t<i class=\"eicon-menu-bar\"><\/i>\t\t\t<\/span>\n\t\t\t<span class=\"e-n-menu-toggle-icon e-close\">\n\t\t\t\t<i class=\"eicon-close\"><\/i>\t\t\t<\/span>\n\t\t<\/button>\n\t\t\t\t\t<div class=\"e-n-menu-wrapper\" id=\"menubar-158\" aria-labelledby=\"menu-toggle-158\">\n\t\t\t\t<ul class=\"e-n-menu-heading\">\n\t\t\t\t\t\t\t\t<li class=\"e-n-menu-item\">\n\t\t\t\t<div id=\"e-n-menu-title-1581\" class=\"e-n-menu-title\">\n\t\t\t\t\t<a class=\"e-n-menu-title-container e-focus e-link\" href=\"https:\/\/redfin.com\/news\/data-center\/\">\t\t\t\t\t\t\t\t\t\t\t\t<span class=\"e-n-menu-title-text\">\n\t\t\t\t\t\t\tData Center\t\t\t\t\t\t<\/span>\n\t\t\t\t\t<\/a>\t\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t\t\t\t<\/li>\n\t\t\t\t\t<li class=\"e-n-menu-item\">\n\t\t\t\t<div id=\"e-n-menu-title-1582\" class=\"e-n-menu-title\">\n\t\t\t\t\t<div class=\"e-n-menu-title-container\">\t\t\t\t\t\t\t\t\t\t\t\t<span class=\"e-n-menu-title-text\">\n\t\t\t\t\t\t\tDashboards\t\t\t\t\t\t<\/span>\n\t\t\t\t\t<\/div>\t\t\t\t\t\t\t\t\t\t\t<button id=\"e-n-menu-dropdown-icon-1582\" class=\"e-n-menu-dropdown-icon e-focus\" data-tab-index=\"2\" aria-haspopup=\"true\" aria-expanded=\"false\" aria-controls=\"e-n-menu-content-1582\" >\n\t\t\t\t\t\t\t<span class=\"e-n-menu-dropdown-icon-opened\">\n\t\t\t\t\t\t\t\t<i aria-hidden=\"true\" class=\"fas fa-angle-up\"><\/i>\t\t\t\t\t\t\t\t<span class=\"elementor-screen-only\">Close Dashboards<\/span>\n\t\t\t\t\t\t\t<\/span>\n\t\t\t\t\t\t\t<span class=\"e-n-menu-dropdown-icon-closed\">\n\t\t\t\t\t\t\t\t<i aria-hidden=\"true\" class=\"fas fa-angle-down\"><\/i>\t\t\t\t\t\t\t\t<span class=\"elementor-screen-only\">Open Dashboards<\/span>\n\t\t\t\t\t\t\t<\/span>\n\t\t\t\t\t\t<\/button>\n\t\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t\t\t\t\t\t<div class=\"e-n-menu-content\">\n\t\t\t\t\t\t<div id=\"e-n-menu-content-1582\" data-tab-index=\"2\" aria-labelledby=\"e-n-menu-dropdown-icon-1582\" class=\"elementor-element elementor-element-50633696 e-con-full e-flex e-con e-child\" data-id=\"50633696\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t<div class=\"elementor-element elementor-element-b69f745 e-con-full e-flex e-con e-child\" data-id=\"b69f745\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-39eb209b elementor-nav-menu--dropdown-none elementor-widget elementor-widget-nav-menu\" data-id=\"39eb209b\" data-element_type=\"widget\" data-e-type=\"widget\" data-settings=\"{&quot;layout&quot;:&quot;vertical&quot;,&quot;submenu_icon&quot;:{&quot;value&quot;:&quot;&lt;i class=\\&quot;fas fa-caret-down\\&quot; aria-hidden=\\&quot;true\\&quot;&gt;&lt;\\\/i&gt;&quot;,&quot;library&quot;:&quot;fa-solid&quot;}}\" data-widget_type=\"nav-menu.default\">\n\t\t\t\t\t\t\t\t<nav aria-label=\"Menu\" class=\"elementor-nav-menu--main elementor-nav-menu__container elementor-nav-menu--layout-vertical e--pointer-none\">\n\t\t\t\t<ul id=\"menu-1-39eb209b\" class=\"elementor-nav-menu sm-vertical\"><li class=\"menu-item menu-item-type-post_type menu-item-object-page menu-item-85181\"><a href=\"https:\/\/www.redfin.com\/news\/data-center\/housing-market\/\" class=\"elementor-item menu-link\">Housing Market Tracker: Weekly &#038; Monthly<\/a><\/li>\n<li class=\"menu-item menu-item-type-post_type menu-item-object-page menu-item-85184\"><a href=\"https:\/\/www.redfin.com\/news\/data-center\/buyers-sellers\/\" class=\"elementor-item menu-link\">Balance of Power: Buyers and Sellers<\/a><\/li>\n<li class=\"menu-item menu-item-type-post_type menu-item-object-page menu-item-85180\"><a href=\"https:\/\/www.redfin.com\/news\/data-center\/luxury-home-market\/\" class=\"elementor-item menu-link\">Luxury Home Market<\/a><\/li>\n<li class=\"menu-item menu-item-type-post_type menu-item-object-page menu-item-85179\"><a href=\"https:\/\/www.redfin.com\/news\/data-center\/price-drops\/\" class=\"elementor-item menu-link\">Price Drops<\/a><\/li>\n<li class=\"menu-item menu-item-type-post_type menu-item-object-page menu-item-85182\"><a href=\"https:\/\/www.redfin.com\/news\/data-center\/home-purchase-cancellations\/\" class=\"elementor-item menu-link\">Home Purchase Cancellations<\/a><\/li>\n<li class=\"menu-item menu-item-type-post_type menu-item-object-page menu-item-85183\"><a href=\"https:\/\/www.redfin.com\/news\/data-center\/home-delistings-relistings\/\" class=\"elementor-item menu-link\">Home Delistings &#038; Relistings<\/a><\/li>\n<\/ul>\t\t\t<\/nav>\n\t\t\t\t\t\t<nav class=\"elementor-nav-menu--dropdown elementor-nav-menu__container\" aria-hidden=\"true\">\n\t\t\t\t<ul id=\"menu-2-39eb209b\" class=\"elementor-nav-menu sm-vertical\"><li class=\"menu-item menu-item-type-post_type menu-item-object-page menu-item-85181\"><a href=\"https:\/\/www.redfin.com\/news\/data-center\/housing-market\/\" class=\"elementor-item menu-link\" tabindex=\"-1\">Housing Market Tracker: Weekly &#038; Monthly<\/a><\/li>\n<li class=\"menu-item menu-item-type-post_type menu-item-object-page menu-item-85184\"><a href=\"https:\/\/www.redfin.com\/news\/data-center\/buyers-sellers\/\" class=\"elementor-item menu-link\" tabindex=\"-1\">Balance of Power: Buyers and Sellers<\/a><\/li>\n<li class=\"menu-item menu-item-type-post_type menu-item-object-page menu-item-85180\"><a href=\"https:\/\/www.redfin.com\/news\/data-center\/luxury-home-market\/\" class=\"elementor-item menu-link\" tabindex=\"-1\">Luxury Home Market<\/a><\/li>\n<li class=\"menu-item menu-item-type-post_type menu-item-object-page menu-item-85179\"><a href=\"https:\/\/www.redfin.com\/news\/data-center\/price-drops\/\" class=\"elementor-item menu-link\" tabindex=\"-1\">Price Drops<\/a><\/li>\n<li class=\"menu-item menu-item-type-post_type menu-item-object-page menu-item-85182\"><a href=\"https:\/\/www.redfin.com\/news\/data-center\/home-purchase-cancellations\/\" class=\"elementor-item menu-link\" tabindex=\"-1\">Home Purchase Cancellations<\/a><\/li>\n<li class=\"menu-item menu-item-type-post_type menu-item-object-page menu-item-85183\"><a href=\"https:\/\/www.redfin.com\/news\/data-center\/home-delistings-relistings\/\" class=\"elementor-item menu-link\" tabindex=\"-1\">Home Delistings &#038; Relistings<\/a><\/li>\n<\/ul>\t\t\t<\/nav>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-39e33ad1 e-con-full e-flex e-con e-child\" data-id=\"39e33ad1\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-4776451d elementor-nav-menu--dropdown-none elementor-widget elementor-widget-nav-menu\" data-id=\"4776451d\" data-element_type=\"widget\" data-e-type=\"widget\" data-settings=\"{&quot;layout&quot;:&quot;vertical&quot;,&quot;submenu_icon&quot;:{&quot;value&quot;:&quot;&lt;i class=\\&quot;fas fa-caret-down\\&quot; aria-hidden=\\&quot;true\\&quot;&gt;&lt;\\\/i&gt;&quot;,&quot;library&quot;:&quot;fa-solid&quot;}}\" data-widget_type=\"nav-menu.default\">\n\t\t\t\t\t\t\t\t<nav aria-label=\"Menu\" class=\"elementor-nav-menu--main elementor-nav-menu__container elementor-nav-menu--layout-vertical e--pointer-none\">\n\t\t\t\t<ul id=\"menu-1-4776451d\" class=\"elementor-nav-menu sm-vertical\"><li class=\"menu-item menu-item-type-post_type menu-item-object-page menu-item-85187\"><a href=\"https:\/\/www.redfin.com\/news\/data-center\/investor-home-purchases\/\" class=\"elementor-item menu-link\">Investor Home Purchases<\/a><\/li>\n<li class=\"menu-item menu-item-type-post_type menu-item-object-page menu-item-85185\"><a href=\"https:\/\/www.redfin.com\/news\/data-center\/starter-home-market\/\" class=\"elementor-item menu-link\">Starter Home Market<\/a><\/li>\n<li class=\"menu-item menu-item-type-post_type menu-item-object-page menu-item-85186\"><a href=\"https:\/\/www.redfin.com\/news\/data-center\/home-price-index\/\" class=\"elementor-item menu-link\">Redfin Home Price Index<\/a><\/li>\n<li class=\"menu-item menu-item-type-post_type menu-item-object-page menu-item-85189\"><a href=\"https:\/\/www.redfin.com\/news\/data-center\/existing-home-sales\/\" class=\"elementor-item menu-link\">Existing Home Sales<\/a><\/li>\n<li class=\"menu-item menu-item-type-post_type menu-item-object-page menu-item-85188\"><a href=\"https:\/\/www.redfin.com\/news\/data-center\/financing-trends\/\" class=\"elementor-item menu-link\">Financing Trends: Cash Purchases, Loan Types &#038; Down Payments<\/a><\/li>\n<\/ul>\t\t\t<\/nav>\n\t\t\t\t\t\t<nav class=\"elementor-nav-menu--dropdown elementor-nav-menu__container\" aria-hidden=\"true\">\n\t\t\t\t<ul id=\"menu-2-4776451d\" class=\"elementor-nav-menu sm-vertical\"><li class=\"menu-item menu-item-type-post_type menu-item-object-page menu-item-85187\"><a href=\"https:\/\/www.redfin.com\/news\/data-center\/investor-home-purchases\/\" class=\"elementor-item menu-link\" tabindex=\"-1\">Investor Home Purchases<\/a><\/li>\n<li class=\"menu-item menu-item-type-post_type menu-item-object-page menu-item-85185\"><a href=\"https:\/\/www.redfin.com\/news\/data-center\/starter-home-market\/\" class=\"elementor-item menu-link\" tabindex=\"-1\">Starter Home Market<\/a><\/li>\n<li class=\"menu-item menu-item-type-post_type menu-item-object-page menu-item-85186\"><a href=\"https:\/\/www.redfin.com\/news\/data-center\/home-price-index\/\" class=\"elementor-item menu-link\" tabindex=\"-1\">Redfin Home Price Index<\/a><\/li>\n<li class=\"menu-item menu-item-type-post_type menu-item-object-page menu-item-85189\"><a href=\"https:\/\/www.redfin.com\/news\/data-center\/existing-home-sales\/\" class=\"elementor-item menu-link\" tabindex=\"-1\">Existing Home Sales<\/a><\/li>\n<li class=\"menu-item menu-item-type-post_type menu-item-object-page menu-item-85188\"><a href=\"https:\/\/www.redfin.com\/news\/data-center\/financing-trends\/\" class=\"elementor-item menu-link\" tabindex=\"-1\">Financing Trends: Cash Purchases, Loan Types &#038; Down Payments<\/a><\/li>\n<\/ul>\t\t\t<\/nav>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t\t\t<\/div>\n\t\t\t\t\t\t\t<\/li>\n\t\t\t\t\t<li class=\"e-n-menu-item\">\n\t\t\t\t<div id=\"e-n-menu-title-1583\" class=\"e-n-menu-title\">\n\t\t\t\t\t<a class=\"e-n-menu-title-container e-focus e-link\" href=\"https:\/\/redfin.com\/news\/data-center\/downloads\/\">\t\t\t\t\t\t\t\t\t\t\t\t<span class=\"e-n-menu-title-text\">\n\t\t\t\t\t\t\tDownloads\t\t\t\t\t\t<\/span>\n\t\t\t\t\t<\/a>\t\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t\t\t\t<\/li>\n\t\t\t\t\t<li class=\"e-n-menu-item\">\n\t\t\t\t<div id=\"e-n-menu-title-1584\" class=\"e-n-menu-title\">\n\t\t\t\t\t<a class=\"e-n-menu-title-container e-focus e-link\" href=\"https:\/\/redfin.com\/news\/data-center\/methodology\/\">\t\t\t\t\t\t\t\t\t\t\t\t<span class=\"e-n-menu-title-text\">\n\t\t\t\t\t\t\tMethodology\t\t\t\t\t\t<\/span>\n\t\t\t\t\t<\/a>\t\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t\t\t\t<\/li>\n\t\t\t\t\t\t<\/ul>\n\t\t\t<\/div>\n\t\t<\/nav>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-3d7ea12d e-flex e-con-boxed e-con e-parent\" data-id=\"3d7ea12d\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t<div class=\"elementor-element elementor-element-3875cd54 e-con-full e-flex e-con e-child\" data-id=\"3875cd54\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-3b769059 elementor-widget elementor-widget-breadcrumbs\" data-id=\"3b769059\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"breadcrumbs.default\">\n\t\t\t\t\t<p id=\"breadcrumbs\"><span><span><a href=\"https:\/\/www.redfin.com\/news\/\">Home<\/a><\/span><\/span><\/p>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-6d89299 e-flex e-con-boxed e-con e-parent\" data-id=\"6d89299\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t<div class=\"elementor-element elementor-element-eadd71b e-con-full e-flex e-con e-child\" data-id=\"eadd71b\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-39ddf32 elementor-widget elementor-widget-heading\" data-id=\"39ddf32\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t\t<h1 class=\"elementor-heading-title elementor-size-default\"><span style=\"color: var(--e-global-color-primary)\">Redfin<\/span> Data Center Download Hub<\/h1>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-b884e9d elementor-widget elementor-widget-heading\" data-id=\"b884e9d\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Download housing market insights from across the U.S.<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-8f0e182 e-flex e-con-boxed e-con e-parent\" data-id=\"8f0e182\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-92f3a83 elementor-widget elementor-widget-html\" data-id=\"92f3a83\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>Redfin Data Center &mdash; Downloads<\/title>\n<style>\n  .dc-downloads {\n    --red: #D92228;\n    --red-hover: #b51c21;\n    --bg: #f7f7f9;\n    --card: #ffffff;\n    --dark: #222222;\n    --gray: #5e6d77;\n    --light-gray: #767676;\n    --border: #d1d5d8;\n    --disabled: #dcdcdc;\n    font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif;\n    color: #222222;\n    line-height: 1.5;\n    margin: 0 auto;\n  }\n  .dc-downloads * { box-sizing: border-box; }\n\n  \/* Grid \u2014 stretch so cards in the same row share height *\/\n  .dc-downloads .grid {\n    display: grid;\n    grid-template-columns: repeat(3, 1fr);\n    gap: 20px;\n    align-items: stretch;\n  }\n  @media (max-width: 900px) { .dc-downloads .grid { grid-template-columns: repeat(2, 1fr); } }\n  @media (max-width: 580px) { .dc-downloads .grid { grid-template-columns: 1fr; } }\n\n  \/* Card *\/\n  .dc-downloads .card {\n    background: var(--card);\n    border-radius: 8px;\n    box-shadow: 0 4px 16px rgba(0,0,0,0.12);\n    padding: 20px;\n    display: flex;\n    flex-direction: column;\n  }\n  .dc-downloads .card-title { font-size: 17px; font-weight: 700; margin-bottom: 3px; }\n  .dc-downloads .card-badge {\n    font-size: 10px; font-weight: 700; text-transform: uppercase;\n    letter-spacing: 0.5px; color: var(--light-gray); margin-bottom: 4px;\n  }\n  .dc-downloads .card-last-updated {\n    font-size: 11px; color: var(--light-gray); margin-bottom: 8px; display: none;\n  }\n  .dc-downloads .card-desc { font-size: 13px; color: var(--gray); margin-bottom: 14px; }\n\n  \/* Frequency toggle *\/\n  .dc-downloads .freq-toggle { display: flex; gap: 4px; margin-bottom: 14px; }\n  .dc-downloads .freq-btn {\n    padding: 5px 14px; border-radius: 6px; font-size: 12px; font-weight: 600;\n    cursor: pointer; border: 1px solid #b8bdc4;\n    background: white; color: var(--dark); transition: all 0.15s;\n    box-shadow: 0 1px 2px rgba(0,0,0,0.08);\n  }\n  .dc-downloads .freq-btn:hover:not(.active) {\n    background: #ecedef; border-color: var(--dark);\n    box-shadow: 0 2px 4px rgba(0,0,0,0.12);\n  }\n  .dc-downloads .freq-btn.active { background: var(--dark); color: white; border-color: var(--dark); box-shadow: none; }\n\n  \/* Column mode + suffix toggles *\/\n  .dc-downloads .col-mode-row { display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px; }\n  .dc-downloads .col-mode-label { font-size: 11px; color: var(--gray); }\n  .dc-downloads .col-mode-toggle, .dc-downloads .suffix-toggle { display: flex; gap: 4px; }\n  .dc-downloads .col-mode-btn, .dc-downloads .suffix-btn {\n    padding: 4px 12px; border-radius: 6px; font-size: 11px; font-weight: 600;\n    cursor: pointer; border: 1px solid #b8bdc4;\n    background: white; color: var(--dark); transition: all 0.15s;\n    box-shadow: 0 1px 2px rgba(0,0,0,0.08);\n  }\n  .dc-downloads .col-mode-btn:hover:not(.active), .dc-downloads .suffix-btn:hover:not(.active):not(:disabled) {\n    background: #ecedef; border-color: var(--dark);\n    box-shadow: 0 2px 4px rgba(0,0,0,0.12);\n  }\n  .dc-downloads .col-mode-btn.active, .dc-downloads .suffix-btn.active {\n    background: var(--dark); color: white; border-color: var(--dark);\n    box-shadow: none;\n  }\n  .dc-downloads .suffix-btn:disabled { opacity: 0.35; cursor: not-allowed; box-shadow: none; }\n\n  \/* Key metrics info panel *\/\n  \/* Metrics info block (sits near the title; only the toggle is interactive) *\/\n  .dc-downloads .metrics-info {\n    background: #f8f8f8; border: 1px solid var(--border); border-radius: 4px;\n    padding: 12px; margin-bottom: 24px;\n  }\n  .dc-downloads .metrics-info-header {\n    display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px;\n  }\n  .dc-downloads .metrics-info-label {\n    font-size: 11px; font-weight: 600; color: var(--gray);\n    text-transform: uppercase; letter-spacing: 0.3px;\n  }\n  .dc-downloads .metrics-pills {\n    display: none; flex-wrap: wrap; gap: 4px 6px;\n  }\n  .dc-downloads .metrics-pills.visible { display: flex; }\n  .dc-downloads .metric-pill {\n    display: inline-block; padding: 3px 10px; border-radius: 12px;\n    background: #ecedef; color: #4a5560;\n    font-size: 11px; font-weight: 500; line-height: 1.4;\n    cursor: default; user-select: none;\n  }\n  \/* Extra breathing room before the download button *\/\n  .dc-downloads .trends-row { margin-bottom: 20px; }\n\n  \/* Form *\/\n  .dc-downloads .form-group { margin-bottom: 12px; }\n  .dc-downloads .form-label { display: block; font-size: 12px; font-weight: 600; margin-bottom: 5px; }\n\n  .dc-downloads .select-wrapper { position: relative; }\n  .dc-downloads .select-wrapper::after {\n    content: '\\25be'; font-size: 11px; color: var(--gray);\n    position: absolute; right: 9px; top: 50%; transform: translateY(-50%); pointer-events: none;\n  }\n  .dc-downloads select, .dc-downloads input[type=\"text\"] {\n    width: 100%; padding: 9px 28px 9px 11px; border: 1px solid var(--border);\n    border-radius: 4px; font-size: 13px; color: var(--dark);\n    appearance: none; outline: none; background: #fff;\n  }\n  .dc-downloads select:focus, .dc-downloads input[type=\"text\"]:focus { border-color: var(--red); }\n  .dc-downloads select:disabled { background: #f3f3f3; color: var(--light-gray); cursor: not-allowed; }\n\n  \/* Date range *\/\n  .dc-downloads .date-range-row { display: flex; gap: 8px; }\n  .dc-downloads .date-col { flex: 1; }\n  .dc-downloads .date-sublabel {\n    font-size: 10px; font-weight: 700; text-transform: uppercase;\n    letter-spacing: 0.5px; color: var(--light-gray); margin-bottom: 4px;\n  }\n  .dc-downloads .date-selects { display: flex; gap: 5px; }\n  .dc-downloads .date-selects .select-wrapper { flex: 1; }\n  .dc-downloads .date-selects select { padding: 9px 22px 9px 9px; font-size: 12px; }\n\n  \/* Button \u2014 pushed to bottom of card *\/\n  .dc-downloads .btn-download {\n    width: 100%; padding: 11px; margin-top: auto; margin-bottom: 6px;\n    background: var(--disabled); color: #fff; border: none; border-radius: 4px;\n    font-size: 14px; font-weight: 600; cursor: not-allowed;\n    transition: background 0.2s;\n  }\n  .dc-downloads .btn-download.active { background: var(--red); cursor: pointer; }\n  .dc-downloads .btn-download.active:hover { background: var(--red-hover); }\n  .dc-downloads .btn-download.loading { background: #a0a0a0; cursor: wait; }\n\n  .dc-downloads .tiny-text { font-size: 11px; color: var(--light-gray); text-align: center; }\n  .dc-downloads .error-msg { font-size: 12px; color: var(--red); margin-top: 5px; display: none; }\n\n  \/* Coming soon notice *\/\n  .dc-downloads .coming-soon-notice {\n    font-size: 12px; color: #856404; background: #fff8e1;\n    border: 1px solid #ffe082; border-radius: 4px;\n    padding: 8px 10px; margin-bottom: 14px;\n  }\n\n  \/* Tips modal \u2014 dialog is document-level, kept unscoped *\/\n  #tips-dialog {\n    border: none;\n    border-radius: 8px;\n    padding: 0;\n    width: 420px;\n    max-width: 90vw;\n    box-shadow: 0 8px 32px rgba(0,0,0,0.18);\n    font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif;\n    position: fixed;\n    top: 50%;\n    left: 50%;\n    transform: translate(-50%, -50%);\n    margin: 0;\n  }\n  #tips-dialog::backdrop {\n    background: rgba(0,0,0,0.45);\n  }\n  .tips-header {\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n    padding: 16px 20px 12px;\n    border-bottom: 1px solid #d1d5d8;\n  }\n  .tips-header h3 {\n    margin: 0;\n    font-size: 15px;\n    font-weight: 700;\n    color: #222222;\n  }\n  .tips-close {\n    background: none;\n    border: none;\n    font-size: 18px;\n    color: #5e6d77;\n    cursor: pointer;\n    padding: 0;\n    line-height: 1;\n  }\n  .tips-body {\n    padding: 16px 20px;\n  }\n  .tips-body ul {\n    margin: 0;\n    padding: 0;\n    list-style: none;\n  }\n  .tips-body li {\n    font-size: 13px;\n    color: #222222;\n    padding: 7px 0;\n    border-bottom: 1px solid #f0f0f0;\n    display: flex;\n    gap: 10px;\n    align-items: flex-start;\n    line-height: 1.5;\n  }\n  .tips-body li:last-child { border-bottom: none; }\n  .tips-icon { font-size: 15px; flex-shrink: 0; margin-top: 1px; }\n  .tips-footer {\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n    padding: 12px 20px 16px;\n    border-top: 1px solid #d1d5d8;\n  }\n  .tips-dont-show {\n    display: flex;\n    align-items: center;\n    gap: 6px;\n    font-size: 12px;\n    color: #5e6d77;\n    cursor: pointer;\n  }\n  .tips-dont-show input { cursor: pointer; }\n  .tips-download-btn {\n    background: #D92228;\n    color: #fff;\n    border: none;\n    border-radius: 4px;\n    padding: 9px 18px;\n    font-size: 13px;\n    font-weight: 600;\n    cursor: pointer;\n    transition: background 0.2s;\n  }\n  .tips-download-btn:hover { background: #b51c21; }\n<\/style>\n<\/head>\n<body>\n<div class=\"page dc-downloads\">\n  <div class=\"grid\" id=\"grid\">\n    <div class=\"card\" id=\"card-housing_market\"><div class=\"card-title\">Housing Market Tracker<\/div><\/div>\n    <div class=\"card\" id=\"card-buyers_and_sellers\"><div class=\"card-title\">Balance of Power: Buyers and Sellers<\/div><\/div>\n    <div class=\"card\" id=\"card-luxury\"><div class=\"card-title\">Luxury Home Market<\/div><\/div>\n    <div class=\"card\" id=\"card-price_drops\"><div class=\"card-title\">Price Drops<\/div><\/div>\n    <div class=\"card\" id=\"card-contract_cancellations\"><div class=\"card-title\">Home Purchase Cancellations<\/div><\/div>\n    <div class=\"card\" id=\"card-delistings_relistings\"><div class=\"card-title\">Home Delistings &amp; Relistings<\/div><\/div>\n    <div class=\"card\" id=\"card-investors\"><div class=\"card-title\">Investor Home Purchases<\/div><\/div>\n    <div class=\"card\" id=\"card-starter_home\"><div class=\"card-title\">Starter Home Market<\/div><\/div>\n    <div class=\"card\" id=\"card-rhpi\"><div class=\"card-title\">Redfin Home Price Index<\/div><\/div>\n    <div class=\"card\" id=\"card-ehs\"><div class=\"card-title\">Existing Home Sales<\/div><\/div>\n    <div class=\"card\" id=\"card-cash_loan\"><div class=\"card-title\">Financing Trends: Cash Purchases, Loan Types &amp; Down Payments<\/div><\/div>\n  <\/div>\n<\/div>\n\n<dialog id=\"tips-dialog\">\n  <div class=\"tips-header\">\n    <h3>Tips for using this data<\/h3>\n    <button class=\"tips-close\" id=\"tips-close-btn\" aria-label=\"Close\">&times;<\/button>\n  <\/div>\n  <div class=\"tips-body\">\n    <ul>\n      <li>\n        <span class=\"tips-icon\">&#128269;<\/span>\n        <span><strong>Find a region:<\/strong> Press <strong>Cmd+F<\/strong> (Mac) or <strong>Ctrl+F<\/strong> (PC) and search by name in the <em>Region Name<\/em> column.<\/span>\n      <\/li>\n      <li>\n        <span class=\"tips-icon\">&#128196;<\/span>\n        <span><strong>Data structure:<\/strong> Each row represents one region for one time period, defined by the <em>Period Begin<\/em> and <em>Period End<\/em> columns.<\/span>\n      <\/li>\n      <li>\n        <span class=\"tips-icon\">&#128288;<\/span>\n        <span><strong>Column suffixes:<\/strong> <em>YOY<\/em> = year-over-year change, <em>WOW<\/em> = week-over-week change, <em>MOM<\/em> = month-over-month change.<\/span>\n      <\/li>\n      <li>\n        <span class=\"tips-icon\">&#128200;<\/span>\n        <span><strong>Rank regions:<\/strong> Sort any metric column to compare regions &mdash; e.g. sort <em>Median Sale Price<\/em> descending to find the most expensive markets.<\/span>\n      <\/li>\n      <li>\n        <span class=\"tips-icon\">&#8211;<\/span>\n        <span><strong>Missing values:<\/strong> <em>NA<\/em> means no data was available for that region or time period.<\/span>\n      <\/li>\n    <\/ul>\n  <\/div>\n  <div class=\"tips-footer\">\n    <label class=\"tips-dont-show\">\n      <input type=\"checkbox\" id=\"tips-dont-show-cb\">\n      Don't show again\n    <\/label>\n    <button class=\"tips-download-btn\" id=\"tips-download-btn\">Download Data<\/button>\n  <\/div>\n<\/dialog>\n\n<script>\nconst S3 = 'https:\/\/redfin-public-data.s3.us-west-2.amazonaws.com\/redfin_data_center';\n\n\/\/ ---------------------------------------------------------------------------\n\/\/ Card configurations\n\/\/ Derived at init() \u2014 no wait required:\n\/\/   badge     \u2192 freqToggle[0].badge (via FREQ_DISPLAY)\n\/\/   pillsSrc  \u2192 first metro 'all' CSV from geosByFreq\n\/\/   freqToggle label \/ dateType \/ badge \u2192 FREQ_DISPLAY lookup by key\n\/\/ Geo paths in geosByFreq are hardcoded as instant fallback; index.json\n\/\/ overrides them at runtime once the async fetch completes.\n\/\/ ---------------------------------------------------------------------------\nconst CARDS = [\n  {\n    id: 'housing_market',\n    title: 'Housing Market Tracker',\n    desc: 'Comprehensive housing market data for residential homes across the U.S., including sales, pricing, inventory, and more.',\n    dateCol: 'period_begin',\n    freqToggle: [\n      { key: 'monthly',    startYear: 2012 },\n      { key: 'four_weeks', startYear: 2016 },\n    ],\n    keyColumns: [\n      'HOMES SOLD', 'MEDIAN SALE PRICE ($)',\n      'MEDIAN DAYS ON MARKET (DAYS)', 'NEW LISTINGS', 'ACTIVE LISTINGS',\n      'PENDING SALES',\n    ],\n    geosByFreq: {\n      monthly: [\n        { type: 'country',      label: 'United States',  all: 'housing_market\/monthly\/country.csv' },\n        { type: 'state',        label: 'State',          all: 'housing_market\/monthly\/all_states.csv' },\n        { type: 'metro',        label: 'Metro Area',     all: 'housing_market\/monthly\/all_metros.csv',         top50: 'housing_market\/monthly\/top_50_metros.csv' },\n        { type: 'county',       label: 'County',         all: 'housing_market\/monthly\/all_counties.csv',       in_top_50_metros: 'housing_market\/monthly\/counties_in_top_50_metros.csv' },\n        { type: 'city',         label: 'City',           all: 'housing_market\/monthly\/all_cities.csv',         top50: 'housing_market\/monthly\/top_50_cities.csv' },\n        { type: 'zip',          label: 'Zip Code',       all: 'housing_market\/monthly\/all_zips.csv',           in_top_50_metros: 'housing_market\/monthly\/zips_in_top_50_metros.csv' },\n        { type: 'neighborhood', label: 'Neighborhood',   all: 'housing_market\/monthly\/all_neighborhoods.csv',  in_top_50_metros: 'housing_market\/monthly\/nbhds_in_top_50_metros.csv' },\n      ],\n      four_weeks: [\n        { type: 'country', label: 'United States', all: 'housing_market\/weekly\/country.csv' },\n        { type: 'metro',   label: 'Metro Area',    all: 'housing_market\/weekly\/all_metros.csv', coverageLabel: 'Top 50 most populous metro areas' },\n      ],\n    },\n  },\n  {\n    id: 'buyers_and_sellers',\n    title: 'Balance of Power: Buyers and Sellers',\n    desc: 'Seasonally adjusted monthly index measuring whether conditions favor buyers or sellers across U.S. metro areas.',\n    dateCol: 'period_begin',\n    noMom: true,\n    noWow: true,\n    noMetricToggle: true,\n    keyColumns: ['BUYERS', 'SELLERS', 'BUYER-SELLER RATIO', 'SELLER-BUYER % DIFFERENCE'],\n    startYear: 2016,\n    dateType: 'monthly',\n    badge: 'Updated Monthly',\n    geos: [\n      { type: 'country',       label: 'United States', all: 'buyers_and_sellers\/monthly\/country.csv' },\n      { type: 'census_region', label: 'Census Region', all: 'buyers_and_sellers\/monthly\/all_census_regions.csv' },\n      { type: 'metro',         label: 'Metro Area',    all: 'buyers_and_sellers\/monthly\/top_50_metros.csv', coverageLabel: 'Top 50 most populous metro areas' },\n    ],\n  },\n  {\n    id: 'luxury',\n    title: 'Luxury Home Market',\n    desc: 'Monthly luxury and non-luxury home market activity across U.S. metros and states, including sales, prices, listings, and days on market.',\n    dateCol: 'period_begin',\n    noMom: true,\n    noWow: true,\n    keyColumns: ['MEDIAN SALE PRICE ($)', 'PENDING SALES', 'ACTIVE LISTINGS', 'MEDIAN DAYS ON MARKET (DAYS)'],\n    excludeColumns: ['price bucket'],\n    freqToggle: [\n      { key: 'luxury',     label: 'Luxury',     startYear: 2013, dateType: 'monthly', badge: 'Updated Monthly' },\n      { key: 'non_luxury', label: 'Non-Luxury', startYear: 2013, dateType: 'monthly', badge: 'Updated Monthly' },\n      { key: 'both',       label: 'Both',       startYear: 2013, dateType: 'monthly', badge: 'Updated Monthly' },\n    ],\n    geosByFreq: {\n      luxury: [\n        { type: 'country', label: 'United States', all: 'luxury\/luxury\/country.csv' },\n        { type: 'state',   label: 'State',         all: 'luxury\/luxury\/state.csv' },\n        { type: 'metro',   label: 'Metro Area',    all: 'luxury\/luxury\/all_metros.csv', coverageLabel: 'Top 50 most populous metro areas' },\n      ],\n      non_luxury: [\n        { type: 'country', label: 'United States', all: 'luxury\/non_luxury\/country.csv' },\n        { type: 'state',   label: 'State',         all: 'luxury\/non_luxury\/state.csv' },\n        { type: 'metro',   label: 'Metro Area',    all: 'luxury\/non_luxury\/all_metros.csv', coverageLabel: 'Top 50 most populous metro areas' },\n      ],\n      both: [\n        { type: 'country', label: 'United States', all: 'luxury\/both\/country.csv' },\n        { type: 'state',   label: 'State',         all: 'luxury\/both\/state.csv' },\n        { type: 'metro',   label: 'Metro Area',    all: 'luxury\/both\/all_metros.csv', coverageLabel: 'Top 50 most populous metro areas' },\n      ],\n    },\n  },\n  {\n    id: 'price_drops',\n    title: 'Price Drops',\n    desc: 'Price drop data for residential homes across the U.S., including count of listings with price drops, drop magnitude, and share of active and sold listings affected.',\n    dateCol: 'period_begin',\n    freqToggle: [\n      { key: 'monthly',    startYear: 2012 },\n      { key: 'four_weeks', startYear: 2016 },\n    ],\n    keyColumns: [\n      'PRICE DROPS', 'PERCENT ACTIVE WITH PRICE DROPS (%)', 'AVERAGE SIZE OF PRICE DROP (%)',\n    ],\n    noMetricToggle: true,\n    geosByFreq: {\n      monthly: [\n        { type: 'country',      label: 'United States',  all: 'price_drops\/monthly\/country.csv' },\n        { type: 'state',        label: 'State',          all: 'price_drops\/monthly\/all_states.csv' },\n        { type: 'metro',        label: 'Metro Area',     all: 'price_drops\/monthly\/all_metros.csv',         top50: 'price_drops\/monthly\/top_50_metros.csv' },\n        { type: 'county',       label: 'County',         all: 'price_drops\/monthly\/all_counties.csv',       in_top_50_metros: 'price_drops\/monthly\/counties_in_top_50_metros.csv' },\n        { type: 'city',         label: 'City',           all: 'price_drops\/monthly\/all_cities.csv',         top50: 'price_drops\/monthly\/top_50_cities.csv' },\n        { type: 'zip',          label: 'Zip Code',       all: 'price_drops\/monthly\/all_zips.csv',           in_top_50_metros: 'price_drops\/monthly\/zips_in_top_50_metros.csv' },\n        { type: 'neighborhood', label: 'Neighborhood',   all: 'price_drops\/monthly\/all_neighborhoods.csv',  in_top_50_metros: 'price_drops\/monthly\/nbhds_in_top_50_metros.csv' },\n      ],\n      four_weeks: [\n        { type: 'country', label: 'United States', all: 'price_drops\/weekly\/country.csv' },\n        { type: 'metro',   label: 'Metro Area',    all: 'price_drops\/weekly\/all_metros.csv', coverageLabel: 'Top 50 most populous metro areas' },\n      ],\n    },\n  },\n  {\n    id: 'contract_cancellations',\n    title: 'Home Purchase Cancellations',\n    desc: 'Contract cancellations for residential homes across the U.S., including count and cancellation rate as a share of pending sales.',\n    dateCol: 'period_begin',\n    freqToggle: [\n      { key: 'monthly',    startYear: 2012 },\n      { key: 'four_weeks', startYear: 2016 },\n    ],\n    keyColumns: [\n      'HOME PURCHASE CANCELLATIONS', 'PERCENT OF PENDING SALES (%)',\n    ],\n    noMetricToggle: true,\n    geosByFreq: {\n      monthly: [\n        { type: 'country',      label: 'United States',  all: 'contract_cancellations\/monthly\/country.csv' },\n        { type: 'state',        label: 'State',          all: 'contract_cancellations\/monthly\/all_states.csv' },\n        { type: 'metro',        label: 'Metro Area',     all: 'contract_cancellations\/monthly\/all_metros.csv',         top50: 'contract_cancellations\/monthly\/top_50_metros.csv' },\n        { type: 'county',       label: 'County',         all: 'contract_cancellations\/monthly\/all_counties.csv',       in_top_50_metros: 'contract_cancellations\/monthly\/counties_in_top_50_metros.csv' },\n        { type: 'city',         label: 'City',           all: 'contract_cancellations\/monthly\/all_cities.csv',         top50: 'contract_cancellations\/monthly\/top_50_cities.csv' },\n        { type: 'zip',          label: 'Zip Code',       all: 'contract_cancellations\/monthly\/all_zips.csv',           in_top_50_metros: 'contract_cancellations\/monthly\/zips_in_top_50_metros.csv' },\n        { type: 'neighborhood', label: 'Neighborhood',   all: 'contract_cancellations\/monthly\/all_neighborhoods.csv',  in_top_50_metros: 'contract_cancellations\/monthly\/nbhds_in_top_50_metros.csv' },\n      ],\n      four_weeks: [\n        { type: 'country', label: 'United States', all: 'contract_cancellations\/weekly\/country.csv' },\n        { type: 'metro',   label: 'Metro Area',    all: 'contract_cancellations\/weekly\/all_metros.csv', coverageLabel: 'Top 50 most populous metro areas' },\n      ],\n    },\n  },\n  {\n    id: 'delistings_relistings',\n    title: 'Home Delistings & Relistings',\n    desc: 'Delisting and relisting activity for residential homes across the U.S., including permanent delistings, relistings, days on market before delisting, and time to relist.',\n    dateCol: 'period_begin',\n    freqToggle: [\n      { key: 'monthly',    startYear: 2012 },\n      { key: 'four_weeks', startYear: 2016 },\n    ],\n    keyColumns: [\n      'TOTAL DELISTINGS', 'SHARE OF LISTINGS DELISTED (%)',\n      'TOTAL RELISTINGS', 'SHARE OF LISTINGS RELISTED (%)',\n    ],\n    noMetricToggle: true,\n    geosByFreq: {\n      monthly: [\n        { type: 'country',      label: 'United States',  all: 'delistings_relistings\/monthly\/country.csv' },\n        { type: 'state',        label: 'State',          all: 'delistings_relistings\/monthly\/all_states.csv' },\n        { type: 'metro',        label: 'Metro Area',     all: 'delistings_relistings\/monthly\/all_metros.csv',         top50: 'delistings_relistings\/monthly\/top_50_metros.csv' },\n        { type: 'county',       label: 'County',         all: 'delistings_relistings\/monthly\/all_counties.csv',       in_top_50_metros: 'delistings_relistings\/monthly\/counties_in_top_50_metros.csv' },\n        { type: 'city',         label: 'City',           all: 'delistings_relistings\/monthly\/all_cities.csv',         top50: 'delistings_relistings\/monthly\/top_50_cities.csv' },\n        { type: 'zip',          label: 'Zip Code',       all: 'delistings_relistings\/monthly\/all_zips.csv',           in_top_50_metros: 'delistings_relistings\/monthly\/zips_in_top_50_metros.csv' },\n        { type: 'neighborhood', label: 'Neighborhood',   all: 'delistings_relistings\/monthly\/all_neighborhoods.csv',  in_top_50_metros: 'delistings_relistings\/monthly\/nbhds_in_top_50_metros.csv' },\n      ],\n      four_weeks: [\n        { type: 'country', label: 'United States', all: 'delistings_relistings\/weekly\/country.csv' },\n        { type: 'metro',   label: 'Metro Area',    all: 'delistings_relistings\/weekly\/all_metros.csv', coverageLabel: 'Top 50 most populous metro areas' },\n      ],\n    },\n  },\n  {\n    id: 'investors',\n    title: 'Investor Home Purchases',\n    desc: 'Quarterly investor home purchase activity and market share across U.S. metros, with breakdowns by price tier and property type.',\n    dateCol: 'period_begin',\n    noMetricToggle: true,\n    noMom: true,\n    noWow: true,\n    freqToggle: [\n      { key: 'by_metro',    startYear: 2000, geoLabel: 'Select Geography', geoPlaceholder: 'Select geography type...' },\n      { key: 'by_category', startYear: 2000, geoLabel: 'Select Category',  geoPlaceholder: 'Select category type...'  },\n    ],\n    keyColumnsByFreq: {\n      by_metro:    ['INVESTOR HOME PURCHASES', 'INVESTOR MARKET SHARE (%)'],\n      by_category: ['INVESTOR HOME PURCHASES', 'INVESTOR MARKET SHARE (%)', 'SHARE OF INVESTOR HOME PURCHASES (%)'],\n    },\n    geosByFreq: {\n      by_metro: [\n        { type: 'country', label: 'United States', all: 'investors\/by_metro\/country.csv' },\n        { type: 'metro',   label: 'Metro Area',    all: 'investors\/by_metro\/all_metros.csv', coverageLabel: 'Available Metros' },\n      ],\n      by_category: [\n        { type: 'price_tier',    label: 'Price Tier',    all: 'investors\/by_category\/price_tier.csv' },\n        { type: 'property_type', label: 'Property Type', all: 'investors\/by_category\/property_type.csv' },\n      ],\n    },\n  },\n  {\n    id: 'starter_home',\n    title: 'Starter Home Market',\n    desc: 'Entry-level home market activity (5th\\u201335th percentile by sale price) across U.S. metros and states, with sales, prices, listings, and days on market.',\n    dateCol: 'period_begin',\n    noMom: true,\n    noWow: true,\n    keyColumns: ['MEDIAN SALE PRICE ($)', 'PENDING SALES', 'ACTIVE LISTINGS', 'MEDIAN DAYS ON MARKET (DAYS)'],\n    startYear: 2013,\n    dateType: 'monthly',\n    badge: 'Updated Monthly',\n    pillsSrc: 'starter_homes\/all_metros.csv',\n    geos: [\n      { type: 'country', label: 'United States', all: 'starter_homes\/country.csv' },\n      { type: 'state',   label: 'State',         all: 'starter_homes\/state.csv' },\n      { type: 'metro',   label: 'Metro Area',     all: 'starter_homes\/all_metros.csv', coverageLabel: 'Top 50 most populous metro areas' },\n    ],\n  },\n  {\n    id: 'rhpi',\n    title: 'Redfin Home Price Index',\n    dateCol: 'period_begin',\n    startYear: 2012,\n    dateType: 'monthly',\n    badge: 'Updated Monthly',\n    keyColumns: [\n      'REDFIN HOME PRICE INDEX',\n    ],\n    noMetricToggle: true,\n    noWow: true,\n    pillsSrc: 'rhpi\/monthly\/all_metros.csv',\n    geos: [\n      { type: 'country', label: 'United States', all: 'rhpi\/monthly\/country.csv' },\n      { type: 'metro',   label: 'Metro Area',    all: 'rhpi\/monthly\/all_metros.csv', coverageLabel: 'Top 50 most populous metro areas' },\n    ],\n  },\n  {\n    id: 'ehs',\n    title: 'Existing Home Sales',\n    badge: 'Updated Monthly',\n    desc: 'Closed transactions for previously owned homes, NAR-adjusted and seasonally adjusted monthly.',\n    noMom: true,\n    noWow: true,\n    noMetricToggle: true,\n    keyColumns: ['EXISTING HOME SALES', 'SEASONALLY ADJUSTED ANNUAL RATE'],\n    startYear: 2010,\n    dateType: 'monthly',\n    dateCol: 'period_begin',\n    pillsSrc: 'ehs\/monthly\/all_census_regions.csv',\n    geos: [\n      { type: 'country',       label: 'United States', all: 'ehs\/monthly\/country.csv' },\n      { type: 'census_region', label: 'Census Region', all: 'ehs\/monthly\/all_census_regions.csv' },\n    ],\n  },\n  {\n    id: 'cash_loan',\n    title: 'Financing Trends: Cash Purchases, Loan Types & Down Payments',\n    desc: 'Monthly trends in home purchase financing across U.S. metros \\u2014 share of all-cash buyers, down payments, and loan type breakdown (conventional, FHA, VA, jumbo).',\n    dateCol: 'period_begin',\n    noMom: true,\n    noWow: true,\n    noMetricToggle: true,\n    keyColumns: [\n      'PERCENT ALL CASH (%)',\n      'MEDIAN DOWN PAYMENT ($)',\n      'MEDIAN DOWN PAYMENT PCT (%)',\n      'PERCENT FHA LOAN (%)',\n      'PERCENT VA LOAN (%)',\n      'PERCENT CONVENTIONAL LOAN (%)',\n      'PERCENT CONVENTIONAL CONFORMING LOAN (%)',\n      'PERCENT CONVENTIONAL JUMBO LOAN (%)',\n    ],\n    startYear: 2011,\n    dateType: 'monthly',\n    badge: 'Updated Quarterly',\n    geos: [\n      { type: 'country', label: 'United States', all: 'all_cash_loan_types\/country.csv' },\n      { type: 'metro',   label: 'Metro Area',    all: 'all_cash_loan_types\/all_metros.csv', coverageLabel: 'Available Metros' },\n    ],\n  },\n];\n\n\/\/ ---------------------------------------------------------------------------\n\/\/ Constants\n\/\/ ---------------------------------------------------------------------------\nconst MONTHS   = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];\nconst QUARTERS = ['Q1 (Jan\\u2013Mar)','Q2 (Apr\\u2013Jun)','Q3 (Jul\\u2013Sep)','Q4 (Oct\\u2013Dec)'];\nconst Q_MONTH  = { '01':'01', '02':'04', '03':'07', '04':'10' };\n\nconst METADATA_COLS = new Set([\n  \/\/ Snake_case names (old CSV format \/ SELECT *)\n  'period_begin','period_end','period_duration','region_type','region_type_id',\n  'table_id','is_seasonally_adjusted','region','region_name','city','state',\n  'state_code','property_type','property_type_id','parent_metro_region',\n  'parent_metro_region_metro_code','last_updated','region_id','duration',\n  'frequency','is_new_construction_transaction','source','date',\n  'rolling_12mo_sale_price_bucket','price_tier_label','population_rank',\n  'redfin_metro','series_id',\n  'snapshot_date','parent_region_type_id','parent_region_id','construction_category',\n  \/\/ Human-readable aliases (new CSV format)\n  'last updated','period begin','period end','region type','region name',\n]);\nconst LABEL_OVERRIDES = {\n  ppsf: 'Price Per Sq Ft', median_ppsf: 'Median Price Per Sq Ft',\n  median_dom: 'Median Days on Market', avg_sale_to_list: 'Sale-to-List Ratio',\n  sold_above_list: 'Sold Above List', off_market_in_two_weeks: 'Off Market in 2 Weeks',\n  percent_sold_above_list_price: '% Sold Above List',\n  percent_off_market_in_two_weeks: '% Off Market in 2 Weeks',\n};\nconst PLURALS = { country: 'country', city: 'cities', county: 'counties', neighborhood: 'neighborhoods', zip: 'zips', cbsa: 'cbsas', metro: 'metros', state: 'states', census_region: 'census_regions' };\nfunction pluralize(type) { return PLURALS[type] || type + 's'; }\n\n\/\/ Geo types where MoM is not computed (sub-metro granularity \u2014 too noisy)\nconst GEO_NO_MOM = new Set(['county', 'city', 'zip', 'neighborhood']);\n\nconst GEO_DISPLAY = {\n  country:       'United States',\n  census_region: 'Census Regions (Northeast, Midwest, South, West)',\n  state:         'States',\n  cbsa:          'CBSAs',\n  metro:         'Metro Areas',\n  county:        'Counties',\n  city:          'Cities',\n  zip:           'Zip Codes',\n  neighborhood:  'Neighborhoods',\n  price_tier:    'Price Tier',\n  property_type: 'Property Type',\n};\nfunction geoDisplay(type) { return GEO_DISPLAY[type] || type; }\n\n\/\/ Canonical display metadata per frequency key.\n\/\/ startYear is a fallback; cards override via startYears: { key: year }.\nconst FREQ_DISPLAY = {\n  monthly:     { label: 'Monthly',     dateType: 'monthly',   badge: 'Updated Monthly',   startYear: 2012 },\n  four_weeks:  { label: 'Weekly',      dateType: 'monthly',   badge: 'Updated Weekly',    startYear: 2016 },\n  quarterly:   { label: 'Quarterly',   dateType: 'quarterly', badge: 'Updated Quarterly', startYear: 2000 },\n  by_metro:    { label: 'By Metro',    dateType: 'quarterly', badge: 'Updated Quarterly' },\n  by_category: { label: 'By Category', dateType: 'quarterly', badge: 'Updated Quarterly' },\n  luxury:      { label: 'Luxury',      dateType: 'monthly',   badge: 'Updated Monthly', snapToPrevMonth: true },\n  non_luxury:  { label: 'Non-Luxury',  dateType: 'monthly',   badge: 'Updated Monthly', snapToPrevMonth: true },\n  both:        { label: 'Both',        dateType: 'monthly',   badge: 'Updated Monthly', snapToPrevMonth: true },\n};\n\nfunction toLabel(col) {\n  return LABEL_OVERRIDES[col] || col.split(\/[_\\s]+\/).map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');\n}\n\n\/\/ ---------------------------------------------------------------------------\n\/\/ Render cards\n\/\/ ---------------------------------------------------------------------------\nfunction geoTypeOptions(geos) {\n  return geos.map(g => `<option value=\"${g.type}\">${g.label}<\/option>`).join('');\n}\n\nfunction renderCard(cfg) {\n  const freqToggleHTML = cfg.freqToggle ? `\n    <div class=\"freq-toggle\">\n      ${cfg.freqToggle.map((f, i) =>\n        `<button class=\"freq-btn${i === 0 ? ' active' : ''}\" data-freq=\"${f.key}\">${f.label}<\/button>`\n      ).join('')}\n    <\/div>` : '';\n\n  const initGeos = cfg.geosByFreq ? cfg.geosByFreq[cfg.freqToggle[0].key] : (cfg.geos || []);\n\n  const comingSoonHTML = cfg.comingSoon\n    ? `<div class=\"coming-soon-notice\">&#9888; This dataset is not yet available for download. Check back soon.<\/div>` : '';\n\n  return `\n  <div class=\"card\" id=\"card-${cfg.id}\">\n    <div class=\"card-title\">${cfg.title}<\/div>\n    <div class=\"card-badge\">${cfg.badge}<\/div>\n    ${comingSoonHTML}\n    ${(cfg.keyColumns || cfg.keyColumnsByFreq) && !cfg.comingSoon ? `\n    <div class=\"metrics-info\">\n      <div class=\"metrics-info-header\">\n        <div class=\"metrics-info-label\">Download includes<\/div>\n        ${!cfg.noMetricToggle ? `<div class=\"col-mode-toggle\">\n          <button class=\"col-mode-btn active\" data-mode=\"key\">Key Metrics<\/button>\n          <button class=\"col-mode-btn\" data-mode=\"all\">All Metrics<\/button>\n        <\/div>` : ''}\n      <\/div>\n      <div class=\"metrics-pills key-metrics-pills visible\">\n        ${(cfg.keyColumnsByFreq ? cfg.keyColumnsByFreq[cfg.freqToggle?.[0]?.key] || [] : cfg.keyColumns || []).map(c => `<span class=\"metric-pill\">${c.replace(\/\\s*\\([^)]*\\)\\s*$\/, '').toLowerCase().replace(\/\\b\\w\/g, l => l.toUpperCase()).replace(\/\\bFha\\b\/g, 'FHA').replace(\/\\bVa\\b\/g, 'VA')}<\/span>`).join('')}\n      <\/div>\n      ${!cfg.noMetricToggle ? `<div class=\"metrics-pills all-cols-grid\"><\/div>` : ''}\n    <\/div>` : ''}\n    ${freqToggleHTML}\n    <div class=\"form-group\">\n      <label class=\"form-label geo-type-label\">Select Geography<\/label>\n      <div class=\"select-wrapper\">\n        <select class=\"geo-type-select\"${cfg.comingSoon ? ' disabled' : ''}>\n          <option value=\"\">Select geography type...<\/option>\n          ${geoTypeOptions(initGeos)}\n        <\/select>\n      <\/div>\n    <\/div>\n    <div class=\"form-group coverage-group\" style=\"display:none\">\n      <label class=\"form-label\">Select coverage<\/label>\n      <div class=\"select-wrapper\">\n        <select class=\"coverage-select\"${cfg.comingSoon ? ' disabled' : ''}><\/select>\n      <\/div>\n    <\/div>\n    <div class=\"form-group\">\n      <label class=\"form-label\">Select Date Range<\/label>\n      <div class=\"date-range-row\">\n        <div class=\"date-col\">\n          <div class=\"date-sublabel\">Start<\/div>\n          <div class=\"date-selects\">\n            <div class=\"select-wrapper\"><select class=\"start-a\"${cfg.comingSoon ? ' disabled' : ''}><\/select><\/div>\n            <div class=\"select-wrapper\"><select class=\"start-b\"${cfg.comingSoon ? ' disabled' : ''}><\/select><\/div>\n          <\/div>\n        <\/div>\n        <div class=\"date-col\">\n          <div class=\"date-sublabel\">End<\/div>\n          <div class=\"date-selects\">\n            <div class=\"select-wrapper\"><select class=\"end-a\"${cfg.comingSoon ? ' disabled' : ''}><\/select><\/div>\n            <div class=\"select-wrapper\"><select class=\"end-b\"${cfg.comingSoon ? ' disabled' : ''}><\/select><\/div>\n          <\/div>\n        <\/div>\n      <\/div>\n    <\/div>\n    ${(cfg.keyColumns || cfg.keyColumnsByFreq) && !cfg.comingSoon ? `\n    <div class=\"col-mode-row trends-row\">\n      <span class=\"col-mode-label\">Trends<\/span>\n      <div class=\"suffix-toggle\">\n        <button class=\"suffix-btn\" data-suffix=\"none\">None<\/button>\n        <button class=\"suffix-btn active\" data-suffix=\"yoy\">YoY<\/button>\n        ${!cfg.noMom ? '<button class=\"suffix-btn\" data-suffix=\"mom\">MoM<\/button>' : ''}\n        ${!cfg.noWow ? '<button class=\"suffix-btn\" data-suffix=\"wow\">WoW<\/button>' : ''}\n\n      <\/div>\n    <\/div>` : ''}\n    <button class=\"btn-download\" disabled>${cfg.comingSoon ? 'Coming soon' : 'Download Data'}<\/button>\n    <div class=\"error-msg\"><\/div>\n    <div class=\"tiny-text\">${cfg.comingSoon ? 'Data will be available for download when this dataset launches.' : 'Downloads a filtered CSV for your selected date range'}<\/div>\n  <\/div>`;\n}\n\n\/\/ ---------------------------------------------------------------------------\n\/\/ Date pickers\n\/\/ ---------------------------------------------------------------------------\nfunction pad2(n) { return String(n).padStart(2, '0'); }\nfunction resolveStartYear(freqCfg) {\n  return freqCfg.yearsBack\n    ? new Date().getFullYear() - freqCfg.yearsBack\n    : freqCfg.startYear;\n}\n\n\/\/ Looks up the {start, end} entry for a frequency from index.json's date_ranges.\n\/\/ Multi-freq cards key by freqToggle[].key; single-freq cards key under 'default'.\nfunction dateRangeFor(cfg, freqKey) {\n  if (!cfg.dateRanges) return null;\n  return cfg.dateRanges[freqKey] || cfg.dateRanges['default'] || null;\n}\n\nfunction endOverrideFromRange(range) {\n  if (!range || !range.end) return null;\n  const yr = parseInt(range.end.slice(0, 4));\n  const mo = parseInt(range.end.slice(5, 7));\n  return (isNaN(yr) || isNaN(mo)) ? null : { year: yr, month: mo };\n}\n\nfunction startYearFromRange(range) {\n  if (!range || !range.start) return null;\n  const yr = parseInt(range.start.slice(0, 4));\n  return isNaN(yr) ? null : yr;\n}\n\nfunction initDatePicker(el, dateType, startYear, freqKey, endOverride) {\n  const now = new Date();\n  const curYear = now.getFullYear();\n  const curMonth = now.getMonth() + 1;\n  \/\/ Monthly frequency: last complete month is previous calendar month.\n  \/\/ Weekly (four_weeks) frequency: current month has data (rolling windows extend into it).\n  const isMonthlyFreq = freqKey === 'monthly' || dateType === 'monthly' || (FREQ_DISPLAY[freqKey]?.snapToPrevMonth ?? false);\n  \/\/ Cap end at the actual data extent when known, else fall back to the calendar.\n  const calendarLastMonth = isMonthlyFreq ? (curMonth === 1 ? 12 : curMonth - 1) : curMonth;\n  const calendarLastYear  = isMonthlyFreq && curMonth === 1 ? curYear - 1 : curYear;\n  const lastYear  = endOverride?.year  ?? calendarLastYear;\n  const lastMonth = endOverride?.month ?? calendarLastMonth;\n  const lastCap   = dateType === 'monthly' ? lastMonth : Math.ceil(lastMonth \/ 3);\n  const yearTop   = Math.min(curYear, lastYear);\n\n  function clampEndMonth(selA, selB) {\n    const selectedYear = parseInt(selB.value);\n    Array.from(selA.options).forEach(o => {\n      o.disabled = selectedYear === lastYear && parseInt(o.value) > lastCap;\n    });\n    if (parseInt(selA.value) > lastCap && selectedYear === lastYear) {\n      selA.value = pad2(lastCap);\n    }\n  }\n\n  ['start', 'end'].forEach(prefix => {\n    const selA = el.querySelector(`.${prefix}-a`);\n    const selB = el.querySelector(`.${prefix}-b`);\n    selA.innerHTML = '';\n    selB.innerHTML = '';\n\n    if (dateType === 'monthly') {\n      MONTHS.forEach((m, i) => {\n        const o = document.createElement('option');\n        o.value = pad2(i + 1); o.textContent = m; selA.appendChild(o);\n      });\n    } else {\n      QUARTERS.forEach((q, i) => {\n        const o = document.createElement('option');\n        o.value = pad2(i + 1); o.textContent = q; selA.appendChild(o);\n      });\n    }\n    for (let y = yearTop; y >= startYear; y--) {\n      const o = document.createElement('option');\n      o.value = y; o.textContent = y; selB.appendChild(o);\n    }\n    selA.addEventListener('change', () => checkReady(el));\n    selB.addEventListener('change', () => { clampEndMonth(selA, selB); checkReady(el); });\n  });\n\n  el.querySelector('.start-a').value = '01';\n  el.querySelector('.start-b').value = Math.min(yearTop, lastYear);\n  el.querySelector('.end-a').value = pad2(lastCap);\n  el.querySelector('.end-b').value = lastYear;\n\n  \/\/ Apply initial disabled state on the end month picker.\n  clampEndMonth(el.querySelector('.end-a'), el.querySelector('.end-b'));\n}\n\nfunction getDateRange(el, dateType) {\n  const sa = el.querySelector('.start-a').value;\n  const sb = el.querySelector('.start-b').value;\n  const ea = el.querySelector('.end-a').value;\n  const eb = el.querySelector('.end-b').value;\n  if (dateType === 'monthly') {\n    return { startDate: `${sb}-${sa}-01`, endDate: `${eb}-${ea}-01` };\n  }\n  return { startDate: `${sb}-${Q_MONTH[sa]}-01`, endDate: `${eb}-${Q_MONTH[ea]}-01` };\n}\n\nfunction getDateLabel(el, dateType) {\n  const sa = el.querySelector('.start-a').value;\n  const sb = el.querySelector('.start-b').value;\n  const ea = el.querySelector('.end-a').value;\n  const eb = el.querySelector('.end-b').value;\n  return dateType === 'monthly'\n    ? `${sb}_${MONTHS[+sa-1]}_to_${eb}_${MONTHS[+ea-1]}`\n    : `${sb}_Q${+sa}_to_${eb}_Q${+ea}`;\n}\n\n\/\/ ---------------------------------------------------------------------------\n\/\/ Card logic\n\/\/ ---------------------------------------------------------------------------\nfunction getActiveGeos(el, cfg) {\n  if (!cfg.freqToggle) return cfg.geos || [];\n  const activeKey = el.querySelector('.freq-btn.active').dataset.freq;\n  return (cfg.geosByFreq && cfg.geosByFreq[activeKey]) || cfg.geos || [];\n}\n\nfunction getActiveDateCfg(el, cfg) {\n  if (!cfg.freqToggle) return cfg;\n  const activeKey = el.querySelector('.freq-btn.active').dataset.freq;\n  return cfg.freqToggle.find(f => f.key === activeKey);\n}\n\nfunction getSelectedGeo(el, cfg) {\n  const type = el.querySelector('.geo-type-select').value;\n  return getActiveGeos(el, cfg).find(g => g.type === type) || null;\n}\n\nfunction getSelectedFile(el, cfg) {\n  const geo = getSelectedGeo(el, cfg);\n  if (!geo) return null;\n  const coverage = el.querySelector('.coverage-select').value;\n  if (coverage === 'top50' && geo.top50) return geo.top50;\n  if (coverage === 'in_top_50_metros' && geo.in_top_50_metros) return geo.in_top_50_metros;\n  return geo.all;\n}\n\nfunction checkReady(el) {\n  const cfg = getCardCfg(el);\n  if (!cfg || cfg.comingSoon) { setActive(el, false); return; }\n  const geoFile = getSelectedFile(el, cfg);\n  if (!geoFile) { setActive(el, false); return; }\n  const freqCfg = getActiveDateCfg(el, cfg);\n  const dateType = freqCfg.dateType || cfg.dateType;\n  const { startDate, endDate } = getDateRange(el, dateType);\n  const ok = startDate <= endDate;\n  setActive(el, ok);\n\n  const hint = el.querySelector('.tiny-text');\n  if (hint && ok) {\n    const years = (new Date(endDate) - new Date(startDate)) \/ (1000 * 60 * 60 * 24 * 365.25);\n    const geo = getSelectedGeo(el, cfg);\n    const coverage = el.querySelector('.coverage-select').value;\n    const isAllSmallGeo = geo && (geo.type === 'zip' || geo.type === 'neighborhood' || geo.type === 'city' || geo.type === 'county') && coverage === 'all';\n    const isNbhdInTop50 = geo && geo.type === 'neighborhood' && coverage === 'in_top_50_metros';\n    if (isAllSmallGeo) {\n      hint.textContent = `\u26a0\ufe0f Downloading all ${geoDisplay(geo.type).toLowerCase()} produces a very large file that may be slow to download and open in Excel or Google Sheets. Consider selecting \"${geoDisplay(geo.type)} in Top 50 Metros\" for a smaller file.`;\n    } else if (isNbhdInTop50) {\n      hint.textContent = `\u26a0\ufe0f Downloading neighborhoods in the top 50 metros may take a while and produce a large file. Consider narrowing your date range for a faster download.`;\n    } else if (years > 5) {\n      hint.textContent = `\u26a0\ufe0f Downloading ${Math.round(years)} years of data may produce a very large file that is slow to open in Excel or Google Sheets. Consider narrowing your date range.`;\n    } else {\n      hint.textContent = 'Downloads a filtered CSV for your selected date range';\n    }\n  }\n}\n\nfunction setActive(el, active) {\n  const btn = el.querySelector('.btn-download');\n  btn.disabled = !active;\n  btn.classList.toggle('active', active);\n}\n\nfunction getCardCfg(el) {\n  const id = el.id.replace('card-', '');\n  return CARDS.find(c => c.id === id) || null;\n}\n\n\/\/ Reads the head (header + first data row) and tail (last data row) of a CSV via\n\/\/ Range requests. CSVs are sorted by \"Period Begin\" desc, so the first row is the\n\/\/ latest week\/month and the last row is the earliest. Returns { startYear, endYear,\n\/\/ endMonth, lastUpdated } \u2014 fields are absent if not derivable. End uses Period\n\/\/ End (not Begin) so weekly rolling 4-week windows reflect the month the latest\n\/\/ row ENDED in (matches index.json's date_ranges semantics).\nasync function fetchCsvBounds(s3path) {\n  try {\n    const head = await fetch(`${S3}\/${s3path}`, { method: 'HEAD', cache: 'no-store' });\n    const size = parseInt(head.headers.get('content-length'));\n    if (!size || isNaN(size)) return {};\n    const tailStart = Math.max(0, size - 8192);\n    const [headResp, tailResp] = await Promise.all([\n      fetch(`${S3}\/${s3path}`, { headers: { Range: 'bytes=0-4095' }, cache: 'no-store' }),\n      fetch(`${S3}\/${s3path}`, { headers: { Range: `bytes=${tailStart}-${size - 1}` }, cache: 'no-store' }),\n    ]);\n    const headLines = (await headResp.text()).split('\\n').filter(l => l.trim());\n    const tailLines = (await tailResp.text()).split('\\n').filter(l => l.trim());\n    if (headLines.length < 2 || tailLines.length === 0) return {};\n    const headers = parseCSVLine(headLines[0]);\n    const firstRow = parseCSVLine(headLines[1]);\n    const lastRow  = parseCSVLine(tailLines[tailLines.length - 1]);\n    const idx = (name) => headers.findIndex(h => h.toLowerCase().replace(\/\\s+\/g, '_') === name);\n    const beginIdx = idx('period_begin');\n    const endIdx   = idx('period_end');\n    const luIdx    = idx('last_updated');\n    const out = {};\n    if (endIdx >= 0) {\n      const fy = parseInt((firstRow[endIdx] || '').slice(0, 4));\n      const fm = parseInt((firstRow[endIdx] || '').slice(5, 7));\n      if (!isNaN(fy)) out.endYear  = fy;\n      if (!isNaN(fm)) out.endMonth = fm;\n    }\n    if (beginIdx >= 0) {\n      const ly = parseInt((lastRow[beginIdx] || '').slice(0, 4));\n      if (!isNaN(ly)) out.startYear = ly;\n    }\n    if (luIdx >= 0) out.lastUpdated = firstRow[luIdx] || null;\n    return out;\n  } catch { return {}; }\n}\n\nasync function fetchLastUpdated(s3path) {\n  try {\n    const resp = await fetch(`${S3}\/${s3path}`, { headers: { Range: 'bytes=0-8191' }, cache: 'no-store' });\n    const text = await resp.text();\n    const lines = text.split('\\n').filter(l => l.trim());\n    if (lines.length < 2) return null;\n    const headers = parseCSVLine(lines[0]);\n    const firstRow = parseCSVLine(lines[1]);\n    const col = headers.findIndex(h => h.toLowerCase().replace(\/\\s+\/g, '_') === 'last_updated');\n    if (col < 0) return null;\n    return firstRow[col] || null;\n  } catch { return null; }\n}\n\nfunction formatLastUpdated(dateStr) {\n  if (!dateStr) return null;\n  const d = new Date(dateStr + 'T00:00:00');\n  return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });\n}\n\nfunction updateSuffixToggle(el, freqKey, geoType) {\n  const momBtn = el.querySelector('.suffix-btn[data-suffix=\"mom\"]');\n  const wowBtn = el.querySelector('.suffix-btn[data-suffix=\"wow\"]');\n  if (!wowBtn) return;\n  const isWeekly = freqKey === 'four_weeks';\n  const isQuarterly = FREQ_DISPLAY[freqKey]?.dateType === 'quarterly';\n  if (momBtn) momBtn.disabled = isWeekly || isQuarterly || (!!geoType && GEO_NO_MOM.has(geoType));\n  if (wowBtn) wowBtn.disabled = !isWeekly;\n  const active = el.querySelector('.suffix-btn.active');\n  if (active && active.disabled) {\n    active.classList.remove('active');\n    el.querySelector('.suffix-btn[data-suffix=\"yoy\"]').classList.add('active');\n  }\n}\n\nfunction initCard(cfg) {\n  const el = document.getElementById(`card-${cfg.id}`);\n  if (!el || cfg.comingSoon) return;\n\n  \/\/ Frequency toggle\n  if (cfg.freqToggle) {\n    el.querySelectorAll('.freq-btn').forEach(btn => {\n      btn.addEventListener('click', () => {\n        el.querySelectorAll('.freq-btn').forEach(b => b.classList.remove('active'));\n        btn.classList.add('active');\n        const freqCfg = cfg.freqToggle.find(f => f.key === btn.dataset.freq);\n\n        \/\/ Update badge then fetch Last Updated for this frequency\n        if (freqCfg.badge) el.querySelector('.card-badge').textContent = freqCfg.badge;\n        const luEl = el.querySelector('.card-last-updated');\n        if (luEl) { luEl.textContent = ''; luEl.style.display = 'none'; }\n        const freqGeosSrc = (cfg.geosByFreq && cfg.geosByFreq[btn.dataset.freq]) || cfg.geos || [];\n        const freqPillsSrc = (freqGeosSrc.find(g => g.type === 'metro') || freqGeosSrc[0])?.all;\n        if (freqPillsSrc) {\n          fetchCsvBounds(freqPillsSrc).then(bounds => {\n            const formatted = formatLastUpdated(bounds.lastUpdated);\n            if (formatted) {\n              const badge = el.querySelector('.card-badge');\n              if (badge) badge.textContent = `${freqCfg.badge} \u00b7 Last Updated: ${formatted}`;\n            }\n            if (bounds.endYear && bounds.endMonth && !dateRangeFor(cfg, btn.dataset.freq)) {\n              initDatePicker(\n                el,\n                freqCfg.dateType,\n                bounds.startYear || resolveStartYear(freqCfg) || cfg.startYear,\n                freqCfg.key,\n                { year: bounds.endYear, month: bounds.endMonth },\n              );\n            }\n          });\n        }\n\n        \/\/ Rebuild geo type dropdown and update label\n        const geoTypeSel = el.querySelector('.geo-type-select');\n        const geoTypeLabel = el.querySelector('.geo-type-label');\n        if (geoTypeLabel && freqCfg.geoLabel) geoTypeLabel.textContent = freqCfg.geoLabel;\n        let freqGeos = freqGeosSrc.slice();\n        if (freqCfg.allowedGeoTypes) freqGeos = freqGeos.filter(g => freqCfg.allowedGeoTypes.includes(g.type));\n        const placeholder = freqCfg.geoPlaceholder || 'Select geography type...';\n        geoTypeSel.innerHTML = `<option value=\"\">${placeholder}<\/option>` +\n          geoTypeOptions(freqGeos);\n\n        \/\/ Reset downstream\n        el.querySelector('.coverage-group').style.display = 'none';\n        el.querySelector('.coverage-select').innerHTML = '';\n        el.querySelector('.error-msg').style.display = 'none';\n\n        const freqRange = dateRangeFor(cfg, btn.dataset.freq);\n        initDatePicker(\n          el,\n          freqCfg.dateType,\n          startYearFromRange(freqRange) || resolveStartYear(freqCfg),\n          freqCfg.key,\n          endOverrideFromRange(freqRange),\n        );\n        updateSuffixToggle(el, btn.dataset.freq);\n        if (cfg.keyColumnsByFreq) {\n          const cols = cfg.keyColumnsByFreq[btn.dataset.freq] || [];\n          const pillsContainer = el.querySelector('.key-metrics-pills');\n          if (pillsContainer) {\n            pillsContainer.innerHTML =\n              cols.map(c => `<span class=\"metric-pill\">${c.replace(\/\\s*\\([^)]*\\)\\s*$\/, '').toLowerCase().replace(\/\\b\\w\/g, l => l.toUpperCase()).replace(\/\\bFha\\b\/g, 'FHA').replace(\/\\bVa\\b\/g, 'VA')}<\/span>`).join('');\n          }\n        }\n        checkReady(el);\n      });\n    });\n  }\n\n\n  \/\/ Metrics Key\/All toggle \u2014 swaps the visible pill set AND filters the CSV\n  el.querySelectorAll('.col-mode-btn').forEach(btn => {\n    btn.addEventListener('click', () => {\n      el.querySelectorAll('.col-mode-btn').forEach(b => b.classList.remove('active'));\n      btn.classList.add('active');\n      const isKey = btn.dataset.mode === 'key';\n      el.querySelector('.key-metrics-pills').classList.toggle('visible', isKey);\n      const allGrid = el.querySelector('.all-cols-grid');\n      if (allGrid) allGrid.classList.toggle('visible', !isKey);\n    });\n  });\n\n  \/\/ Suffix toggle (multi-select; None is exclusive)\n  el.querySelectorAll('.suffix-btn').forEach(btn => {\n    btn.addEventListener('click', () => {\n      if (btn.disabled) return;\n      if (btn.dataset.suffix === 'none') {\n        el.querySelectorAll('.suffix-btn').forEach(b => b.classList.remove('active'));\n        btn.classList.add('active');\n      } else {\n        el.querySelector('.suffix-btn[data-suffix=\"none\"]').classList.remove('active');\n        btn.classList.toggle('active');\n        const anyActive = [...el.querySelectorAll('.suffix-btn:not([data-suffix=\"none\"])')].some(b => b.classList.contains('active') && !b.disabled);\n        if (!anyActive) el.querySelector('.suffix-btn[data-suffix=\"none\"]').classList.add('active');\n      }\n    });\n  });\n\n  \/\/ Geography type dropdown\n  el.querySelector('.geo-type-select').addEventListener('change', async () => {\n    const geo = getSelectedGeo(el, cfg);\n    const coverageGroup = el.querySelector('.coverage-group');\n    const coverageSel   = el.querySelector('.coverage-select');\n\n    el.querySelector('.error-msg').style.display = 'none';\n\n    if (!geo) {\n      coverageGroup.style.display = 'none';\n      checkReady(el); return;\n    }\n\n    const geoDisp = geoDisplay(geo.type);\n    let coverageOptions = `<option value=\"all\">${geo.coverageLabel || 'All ' + geoDisp}<\/option>`;\n    if (geo.top50) coverageOptions += `<option value=\"top50\">Top 50 most populous ${geoDisp.toLowerCase()}<\/option>`;\n    if (geo.in_top_50_metros) coverageOptions += `<option value=\"in_top_50_metros\">${geoDisp} in Top 50 Metros<\/option>`;\n    coverageSel.innerHTML = coverageOptions;\n    const NO_COVERAGE_TYPES = new Set(['country', 'price_tier', 'property_type']);\n    coverageGroup.style.display = NO_COVERAGE_TYPES.has(geo.type) ? 'none' : 'block';\n\n    \/\/ Reinit date picker with bounds. Prefer index.json's date_ranges (computed\n    \/\/ upstream from min\/max(\"Period Begin\")). Fall back to fetchCsvBounds only\n    \/\/ when the index lacks dates for this frequency. The CSV fetch still runs\n    \/\/ either way to pull lastUpdated for the badge.\n    const freqCfg  = getActiveDateCfg(el, cfg);\n    const dateType = freqCfg.dateType || cfg.dateType;\n    const range = dateRangeFor(cfg, freqCfg.key || 'default');\n    const bounds = await fetchCsvBounds(geo.all);\n    const endOverride = endOverrideFromRange(range)\n      || ((bounds.endYear && bounds.endMonth) ? { year: bounds.endYear, month: bounds.endMonth } : null);\n    const startYear = startYearFromRange(range)\n      || bounds.startYear\n      || resolveStartYear(freqCfg)\n      || cfg.startYear;\n    initDatePicker(el, dateType, startYear, freqCfg.key, endOverride);\n\n    const baseBadge = freqCfg.badge || cfg.badge;\n    const formatted = formatLastUpdated(bounds.lastUpdated);\n    el.querySelector('.card-badge').textContent = formatted\n      ? `${baseBadge} \\u00b7 Last Updated: ${formatted}`\n      : baseBadge;\n\n    updateSuffixToggle(el, freqCfg.key || '', geo ? geo.type : null);\n    checkReady(el);\n  });\n\n  \/\/ Coverage dropdown\n  el.querySelector('.coverage-select').addEventListener('change', () => checkReady(el));\n\n  \/\/ Date pickers\n  const initFreqCfg = cfg.freqToggle ? cfg.freqToggle[0] : cfg;\n  const initRange = dateRangeFor(cfg, initFreqCfg.key || 'default');\n  initDatePicker(\n    el,\n    initFreqCfg.dateType || cfg.dateType,\n    startYearFromRange(initRange) || resolveStartYear(initFreqCfg) || cfg.startYear,\n    initFreqCfg.key || '',\n    endOverrideFromRange(initRange),\n  );\n\n  \/\/ Disable suffix buttons that don't apply to the initial frequency\n  updateSuffixToggle(el, initFreqCfg.key || '');\n\n  \/\/ Download button\n  el.querySelector('.btn-download').addEventListener('click', () => {\n    if (localStorage.getItem('redfin_dc_tips_seen')) {\n      handleDownload(el, cfg);\n    } else {\n      showTipsModal(el, cfg);\n    }\n  });\n\n  \/\/ Metric pills + initial Last Updated. Date-picker bounds come from\n  \/\/ index.json's date_ranges; fall back to CSV-derived bounds only if the\n  \/\/ index lacks dates for this card.\n  if (cfg.pillsSrc) {\n    loadPills(el, cfg.pillsSrc, cfg.excludeColumns);\n    fetchCsvBounds(cfg.pillsSrc).then(bounds => {\n      const formatted = formatLastUpdated(bounds.lastUpdated);\n      if (formatted) {\n        const badge = el.querySelector('.card-badge');\n        if (badge) badge.textContent = `${cfg.badge} \u00b7 Last Updated: ${formatted}`;\n      }\n      const fcfg = cfg.freqToggle ? cfg.freqToggle[0] : cfg;\n      if (bounds.endYear && bounds.endMonth && !dateRangeFor(cfg, fcfg.key || 'default')) {\n        initDatePicker(\n          el,\n          fcfg.dateType || cfg.dateType,\n          bounds.startYear || resolveStartYear(fcfg) || cfg.startYear,\n          fcfg.key || '',\n          { year: bounds.endYear, month: bounds.endMonth },\n        );\n      }\n    });\n  }\n}\n\n\/\/ ---------------------------------------------------------------------------\n\/\/ Download\n\/\/ ---------------------------------------------------------------------------\nasync function handleDownload(el, cfg) {\n  const geo       = getSelectedGeo(el, cfg);\n  const s3path    = getSelectedFile(el, cfg);\n  const coverage  = el.querySelector('.coverage-select').value;\n  const errEl     = el.querySelector('.error-msg');\n  const btn       = el.querySelector('.btn-download');\n\n  const freqCfg  = getActiveDateCfg(el, cfg);\n  const dateType = freqCfg.dateType || cfg.dateType;\n  const { startDate, endDate } = getDateRange(el, dateType);\n  const dateLabel = getDateLabel(el, dateType);\n\n  errEl.style.display = 'none';\n  btn.classList.add('loading'); btn.classList.remove('active'); btn.disabled = true;\n\n  let dotCount = 0;\n  const interval = setInterval(() => {\n    dotCount = (dotCount + 1) % 4;\n    btn.textContent = 'Fetching data' + '.'.repeat(dotCount);\n  }, 400);\n\n  try {\n    const resp = await fetch(`${S3}\/${s3path}`, { cache: 'no-store' });\n    if (!resp.ok) throw new Error(\n      resp.status === 404\n        ? 'This dataset isn\\u2019t available yet \\u2014 check back soon.'\n        : `Download failed (HTTP ${resp.status}). Please try again.`\n    );\n\n    const reader  = resp.body.getReader();\n    const decoder = new TextDecoder();\n    const escCSV  = v => { const s = v == null ? '' : String(v); return (s.includes(',') || s.includes('\"') || s.includes('\\n')) ? `\"${s.replace(\/\"\/g, '\"\"')}\"` : s; };\n    let buffer     = '';\n    let headers    = null;\n    let dateCol    = -1;\n    let colIndices = null;\n    const csvChunks = [];\n    let matchCount  = 0;\n    let rowCount    = 0;\n\n    const METADATA = new Set(['LAST UPDATED', 'PERIOD BEGIN', 'PERIOD END', 'FREQUENCY', 'REGION TYPE', 'REGION NAME', 'METRO', 'PROPERTY TYPE', 'PRICE BUCKET', 'BALANCE OF POWER']);\n    const CHANGE_SUFFIXES = [' YOY', ' MOM', ' WOW'];\n    \/\/ Strip a trailing \"(unit)\" tag like \"(%)\", \"($)\", \"(ppts)\" so unit-suffixed\n    \/\/ CSV headers (e.g. \"Percent All Cash (%)\") match unit-free key arrays.\n    const stripUnit = h => h.replace(\/\\s*\\([^)]*\\)\\s*$\/, '');\n    const isChangCol = h => CHANGE_SUFFIXES.some(s => stripUnit(h.toUpperCase()).endsWith(s));\n    const changeSuffix = h => CHANGE_SUFFIXES.find(s => stripUnit(h.toUpperCase()).endsWith(s));\n\n    const colModeBtn = el.querySelector('.col-mode-btn.active');\n    const keyMode = colModeBtn && colModeBtn.dataset.mode === 'key' && cfg.keyColumns;\n    const keySet = keyMode ? new Set(cfg.keyColumns.map(k => stripUnit(k.toUpperCase()))) : null;\n\n    const activeSuffixes = new Set(\n      [...el.querySelectorAll('.suffix-btn.active')]\n        .map(b => b.dataset.suffix).filter(s => s !== 'none')\n    );\n\n    while (true) {\n      const { done, value } = await reader.read();\n      buffer += decoder.decode(value || new Uint8Array(), { stream: !done });\n      const lines = buffer.split('\\n');\n      buffer = done ? '' : lines.pop();\n\n      for (const line of lines) {\n        const trimmed = line.replace(\/\\r$\/, '');\n        if (!trimmed) continue;\n        if (!headers) {\n          headers = parseCSVLine(trimmed);\n          dateCol = headers.findIndex(h => h.toLowerCase().replace(\/\\s+\/g, '_') === cfg.dateCol.toLowerCase().replace(\/\\s+\/g, '_'));\n          colIndices = headers.reduce((acc, h, i) => {\n            const upper = h.toUpperCase();\n            const upperStripped = stripUnit(upper);\n            if (isChangCol(upper)) {\n              if (activeSuffixes.size === 0) return acc;\n              const suf = changeSuffix(upper).trim().toLowerCase(); \/\/ 'yoy', 'mom', 'wow'\n              if (!activeSuffixes.has(suf)) return acc;\n              const baseName = upperStripped.slice(0, upperStripped.length - changeSuffix(upper).length);\n              if (keySet && !keySet.has(baseName)) return acc;\n            } else {\n              if (keySet && !keySet.has(upperStripped) && !METADATA.has(upperStripped)) return acc;\n            }\n            acc.push(i);\n            return acc;\n          }, []);\n          if (colIndices.length === headers.length) colIndices = null;\n          const outHeaders = colIndices ? colIndices.map(i => headers[i]) : headers;\n          csvChunks.push(outHeaders.map(escCSV).join(',') + '\\n');\n          continue;\n        }\n        const row = parseCSVLine(trimmed);\n        const dateOk = dateCol < 0 || (row[dateCol] >= startDate && row[dateCol] <= endDate);\n        if (dateOk) {\n          const outRow = colIndices ? colIndices.map(i => row[i]) : row;\n          csvChunks.push(outRow.map(escCSV).join(',') + '\\n');\n          matchCount++;\n        }\n        rowCount++;\n      }\n\n      if (done) break;\n      if (rowCount % 5000 === 0 && rowCount > 0) {\n        btn.textContent = `Filtering\\u2026 ${(rowCount \/ 1000).toFixed(0)}k rows`;\n        await new Promise(r => setTimeout(r, 0));\n      }\n    }\n\n    clearInterval(interval);\n    if (!headers || matchCount === 0) {\n      throw new Error('No data found for the selected date range.');\n    }\n\n    const geoLabel = coverage === 'top50'            ? `top_50_${pluralize(geo.type)}`\n                   : coverage === 'in_top_50_metros' ? `${pluralize(geo.type)}_in_top_50_metros`\n                   : `all_${pluralize(geo.type)}`;\n    const freqLabel = freqCfg.key === 'four_weeks' ? 'weekly' : (freqCfg.key || cfg.dateType || 'monthly');\n    const colSuffix = keyMode ? '_key_metrics' : '';\n    const filename = `redfin_${cfg.id}_${freqLabel}_${geoLabel}${colSuffix}_${dateLabel}.csv`;\n    const blob = new Blob(csvChunks, { type: 'text\/csv;charset=utf-8;' });\n    triggerDownload(blob, filename);\n\n  } catch (err) {\n    clearInterval(interval);\n    errEl.textContent = err.message || 'Download failed. Please try again.';\n    errEl.style.display = 'block';\n  } finally {\n    clearInterval(interval);\n    btn.textContent = 'Download Data';\n    btn.classList.remove('loading');\n    checkReady(el);\n  }\n}\n\n\/\/ ---------------------------------------------------------------------------\n\/\/ CSV utilities\n\/\/ ---------------------------------------------------------------------------\nfunction parseCSVLine(line) {\n  const result = []; let cur = ''; let inQ = false;\n  for (let i = 0; i < line.length; i++) {\n    const ch = line[i];\n    if (ch === '\"') {\n      if (inQ && line[i+1] === '\"') { cur += '\"'; i++; }\n      else { inQ = !inQ; }\n    } else if (ch === ',' && !inQ) {\n      result.push(cur.trim()); cur = '';\n    } else { cur += ch; }\n  }\n  result.push(cur.trim());\n  return result;\n}\n\nfunction toCSVString(headers, rows) {\n  const esc = v => {\n    const s = v == null ? '' : String(v);\n    return (s.includes(',') || s.includes('\"') || s.includes('\\n')) ? `\"${s.replace(\/\"\/g, '\"\"')}\"` : s;\n  };\n  return [headers.map(esc).join(','), ...rows.map(r => r.map(esc).join(','))].join('\\n');\n}\n\nfunction triggerDownload(csvOrBlob, filename) {\n  const blob = csvOrBlob instanceof Blob ? csvOrBlob : new Blob([csvOrBlob], { type: 'text\/csv;charset=utf-8;' });\n  const url  = URL.createObjectURL(blob);\n  const a    = document.createElement('a');\n  a.href = url; a.download = filename;\n  document.body.appendChild(a); a.click();\n  document.body.removeChild(a); URL.revokeObjectURL(url);\n}\n\n\/\/ ---------------------------------------------------------------------------\n\/\/ Tips modal\n\/\/ ---------------------------------------------------------------------------\nlet _tipsEl = null, _tipsCfg = null;\n\nfunction showTipsModal(el, cfg) {\n  _tipsEl = el;\n  _tipsCfg = cfg;\n  document.getElementById('tips-dont-show-cb').checked = false;\n  document.getElementById('tips-dialog').showModal();\n}\n\n(function wireTipsModal() {\n  const dialog   = document.getElementById('tips-dialog');\n  const closeBtn = document.getElementById('tips-close-btn');\n  const dlBtn    = document.getElementById('tips-download-btn');\n  const cb       = document.getElementById('tips-dont-show-cb');\n\n  closeBtn.addEventListener('click', () => dialog.close());\n\n  dialog.addEventListener('click', e => {\n    if (e.target === dialog) dialog.close();\n  });\n\n  dlBtn.addEventListener('click', () => {\n    if (cb.checked) localStorage.setItem('redfin_dc_tips_seen', '1');\n    dialog.close();\n    handleDownload(_tipsEl, _tipsCfg);\n  });\n})();\n\n\/\/ ---------------------------------------------------------------------------\n\/\/ Metric pills\n\/\/ ---------------------------------------------------------------------------\nasync function loadPills(el, pillsSrc, excludeColumns) {\n  \/\/ Strip a trailing \"(unit)\" tag like \"(%)\", \"($)\", \"(ppts)\" before identity\n  \/\/ checks so the metadata\/exclude\/yoy filters work on unit-suffixed headers.\n  const stripUnit = h => h.replace(\/\\s*\\([^)]*\\)\\s*$\/, '');\n  const excludeSet = excludeColumns ? new Set(excludeColumns.map(c => c.toLowerCase())) : null;\n  try {\n    const resp = await fetch(`${S3}\/${pillsSrc}`);\n    if (!resp.ok) throw new Error();\n    const reader = resp.body.getReader();\n    let firstLine = '';\n    while (true) {\n      const { done, value } = await reader.read();\n      firstLine += new TextDecoder().decode(value);\n      if (done || firstLine.includes('\\n')) break;\n    }\n    reader.cancel();\n    \/\/ Always lowercase incoming headers so the filter, METADATA lookup, and\n    \/\/ toLabel re-Title-Case all behave consistently regardless of whether the\n    \/\/ CSV came from an upstream that emits UPPERCASE, Title Case, or snake_case.\n    const hdrs = firstLine.split('\\n')[0].split(',').map(h => h.trim().replace(\/\"\/g, '').toLowerCase());\n    const base = hdrs.filter(h => {\n      const stripped = stripUnit(h);\n      return !METADATA_COLS.has(stripped)\n        && !stripped.endsWith('_mom') && !stripped.endsWith('_yoy') && !stripped.endsWith('_wow')\n        && !stripped.endsWith(' mom') && !stripped.endsWith(' yoy') && !stripped.endsWith(' wow')\n        && !(excludeSet && excludeSet.has(stripped));\n    });\n    if (base.length === 0) throw new Error();\n\n    const allColsGrid = el.querySelector('.all-cols-grid');\n    if (allColsGrid) {\n      allColsGrid.innerHTML = base.map(h => {\n        const stripped = stripUnit(h);\n        const label = toLabel(stripped).replace(\/\\b\\w\/g, l => l.toUpperCase()).replace(\/\\bFha\\b\/g, 'FHA').replace(\/\\bVa\\b\/g, 'VA');\n        return `<span class=\"metric-pill\">${label}<\/span>`;\n      }).join('');\n    }\n    const allBtn = el.querySelector('.col-mode-btn[data-mode=\"all\"]');\n    if (allBtn) allBtn.textContent = `All Metrics (${base.length})`;\n  } catch {}\n}\n\n\/\/ ---------------------------------------------------------------------------\n\/\/ Init \u2014 derive display metadata synchronously (no wait), then fetch\n\/\/ index.json to override geo paths with live data.\n\/\/ ---------------------------------------------------------------------------\nconst grid = document.getElementById('grid');\n\nasync function init() {\n  \/\/ Phase 1 (sync): expand freqToggle from FREQ_DISPLAY, derive badge + pillsSrc\n  CARDS.forEach(cfg => {\n    if (cfg.freqToggle) {\n      cfg.freqToggle.forEach(f => {\n        const fd = FREQ_DISPLAY[f.key] || {};\n        f.label    = fd.label    || f.key;\n        f.dateType = fd.dateType || 'monthly';\n        f.badge    = fd.badge    || '';\n      });\n      cfg.badge = cfg.freqToggle[0].badge;\n    }\n\n    \/\/ pillsSrc: prefer metro all-CSV from first freq, else first available all-CSV\n    if (!cfg.pillsSrc && cfg.freqToggle && cfg.geosByFreq) {\n      const firstGeos = cfg.geosByFreq[cfg.freqToggle[0].key] || [];\n      const geo = firstGeos.find(g => g.type === 'metro') || firstGeos[0];\n      cfg.pillsSrc = geo?.all || null;\n    }\n  });\n\n  \/\/ Phase 2 (async): fetch index.json and override geosByFreq with live paths\n  let index = {};\n  try {\n    const resp = await fetch(`${S3}\/index.json`, { cache: 'no-store' });\n    if (resp.ok) index = await resp.json();\n  } catch {}\n\n  \/\/ Reserved (non-geo) keys at the dataset level in index.json\n  const NON_GEO_KEYS = new Set(['key_metrics', 'key_metrics_by_freq', 'trends', 'date_ranges']);\n\n  \/\/ Canonical dropdown order for geography types. Snowflake's object_construct\n  \/\/ emits keys alphabetically in JSON, so we re-sort here to match the order\n  \/\/ dashboard consumers and Suzanne's mocks expect. Unknown types fall to the\n  \/\/ bottom (rare \u2014 covered cases below should be exhaustive).\n  const GEO_ORDER = ['country', 'census_region', 'metro', 'state', 'city', 'county', 'neighborhood', 'zip', 'price_tier', 'property_type'];\n  const geoRank = (type) => {\n    const i = GEO_ORDER.indexOf(type);\n    return i < 0 ? GEO_ORDER.length : i;\n  };\n  const sortGeos = (entries) => entries.slice().sort(([a], [b]) => geoRank(a) - geoRank(b));\n\n  \/\/ Convert an index.json geo entry to the card-config geo shape. The hardcoded\n  \/\/ entry (when present) acts as a fallback for fields index.json doesn't\n  \/\/ include \u2014 most importantly coverage_label, so a label tweak in the HTML\n  \/\/ takes effect on next page load without waiting for a publish DAG run.\n  const toGeoEntry = (type, paths, hardcoded) => ({\n    type,\n    label:            GEO_DISPLAY[type] || type,\n    all:              typeof paths === 'string' ? paths : paths.all,\n    top50:            (typeof paths === 'object' ? paths.top50 : null) || hardcoded?.top50 || null,\n    in_top_50_metros: (typeof paths === 'object' ? paths.in_top_50_metros : null) || hardcoded?.in_top_50_metros || null,\n    coverageLabel:    (typeof paths === 'object' ? paths.coverage_label : null) || hardcoded?.coverageLabel || undefined,\n  });\n\n  let gridHTML = '';\n  CARDS.forEach(cfg => {\n    \/\/ Multi-freq cards: update geosByFreq from index.json\n    if (cfg.freqToggle && index[cfg.id]) {\n      cfg.freqToggle.forEach(f => {\n        const freqGeos = index[cfg.id][f.key];\n        if (freqGeos) {\n          const prior = cfg.geosByFreq?.[f.key] || [];\n          cfg.geosByFreq[f.key] = sortGeos(Object.entries(freqGeos)).map(([type, paths]) =>\n            toGeoEntry(type, paths, prior.find(g => g.type === type)),\n          );\n        }\n      });\n    }\n\n    \/\/ Non-toggle cards: populate geos from index.json when available\n    if (!cfg.freqToggle && index[cfg.id]) {\n      const prior = cfg.geos || [];\n      cfg.geos = sortGeos(Object.entries(index[cfg.id]).filter(([type]) => !NON_GEO_KEYS.has(type)))\n        .map(([type, paths]) => toGeoEntry(type, paths, prior.find(g => g.type === type)));\n    }\n\n    \/\/ Source-of-truth: key metrics list comes from index.json (generated by\n    \/\/ select_index_json.sql). Falls back to the hardcoded keyColumns in the\n    \/\/ card config if index.json is unavailable or doesn't include them.\n    if (index[cfg.id]) {\n      if (Array.isArray(index[cfg.id].key_metrics)) {\n        cfg.keyColumns = index[cfg.id].key_metrics;\n      }\n      if (index[cfg.id].key_metrics_by_freq) {\n        cfg.keyColumnsByFreq = index[cfg.id].key_metrics_by_freq;\n      }\n      \/\/ trends: which trend suffix buttons to render. If index.json declares\n      \/\/ the list, derive noMom\/noWow from it; otherwise fall back to the\n      \/\/ hardcoded card-config flags.\n      if (Array.isArray(index[cfg.id].trends)) {\n        const trends = new Set(index[cfg.id].trends);\n        cfg.noMom = !trends.has('mom');\n        cfg.noWow = !trends.has('wow');\n      }\n      \/\/ date_ranges: per-frequency `{start, end}` pulled from min\/max(\"Period Begin\")\n      \/\/ on each source view at index-generation time. Drives the date picker's\n      \/\/ default end (and clamp) without per-card hardcoding. Non-freq cards key\n      \/\/ this under 'default'. Falls back to fetchCsvBounds (CSV Range fetch) if\n      \/\/ absent.\n      if (index[cfg.id].date_ranges && typeof index[cfg.id].date_ranges === 'object') {\n        cfg.dateRanges = index[cfg.id].date_ranges;\n      }\n    }\n\n    gridHTML += renderCard(cfg);\n  });\n  grid.innerHTML = gridHTML;\n\n  CARDS.forEach(cfg => initCard(cfg));\n  \n  if (window.location.hash) {\n  setTimeout(() => {\n    const target = document.querySelector(window.location.hash);\n    if (target) {\n      target.scrollIntoView();\n    }\n  }, 100);\n}\n  \n}\n\ninit();\n<\/script>\n<\/body>\n<\/html>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>Redfin Data Center Download Hub Download housing market insights from across the U.S. Redfin Data Center &mdash; Downloads Housing Market Tracker Balance of Power: Buyers and Sellers Luxury Home Market Price Drops Home Purchase Cancellations Home Delistings &amp; Relistings Investor Home Purchases Starter Home Market Redfin Home Price Index Existing Home Sales Financing Trends: Cash [&hellip;]<\/p>\n","protected":false},"author":12371,"featured_media":0,"parent":24,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_acf_changed":false,"site-sidebar-layout":"no-sidebar","site-content-layout":"page-builder","ast-site-content-layout":"full-width-container","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"disabled","ast-breadcrumbs-content":"","ast-featured-img":"disabled","footer-sml-layout":"","ast-disable-related-posts":"","theme-transparent-header-meta":"default","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"set","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"footnotes":""},"coauthors":[163],"class_list":["post-85151","page","type-page","status-publish","hentry"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v24.7 (Yoast SEO v27.5) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Downloads - Redfin<\/title>\n<meta name=\"description\" content=\"Downloadable datasets covering housing market metrics from across the U.S.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.redfin.com\/news\/data-center\/downloads\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Downloads\" \/>\n<meta property=\"og:description\" content=\"Downloadable datasets covering housing market metrics from across the U.S.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.redfin.com\/news\/data-center\/downloads\/\" \/>\n<meta property=\"og:site_name\" content=\"Redfin Real Estate News\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/redfin\" \/>\n<meta property=\"article:modified_time\" content=\"2026-05-07T16:56:59+00:00\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:site\" content=\"@redfin\" \/>\n<meta name=\"twitter:label1\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data1\" content=\"1 minute\" \/>\n\t<meta name=\"twitter:label2\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data2\" content=\"Suzanne Harrison\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.redfin.com/news\\\/data-center\\\/downloads\\\/\",\"url\":\"https:\\\/\\\/www.redfin.com/news\\\/data-center\\\/downloads\\\/\",\"name\":\"Downloads - Redfin\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.redfin.com/news\\\/#website\"},\"datePublished\":\"2026-05-05T23:13:25+00:00\",\"dateModified\":\"2026-05-07T16:56:59+00:00\",\"description\":\"Downloadable datasets covering housing market metrics from across the U.S.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.redfin.com/news\\\/data-center\\\/downloads\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.redfin.com/news\\\/data-center\\\/downloads\\\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.redfin.com/news\\\/data-center\\\/downloads\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.redfin.com/news\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Data Center\",\"item\":\"https:\\\/\\\/www.redfin.com/news\\\/data-center\\\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Downloads\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.redfin.com/news\\\/#website\",\"url\":\"https:\\\/\\\/www.redfin.com/news\\\/\",\"name\":\"Redfin Real Estate News\",\"description\":\"The latest real estate news and research from technology-powered residential real estate company, Redfin.\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.redfin.com/news\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.redfin.com/news\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/www.redfin.com/news\\\/#organization\",\"name\":\"Redfin\",\"url\":\"https:\\\/\\\/www.redfin.com/news\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.redfin.com/news\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/www.redfin.com\\\/news\\\/wp-content\\\/uploads\\\/2020\\\/10\\\/Redfin-News-Logo.png\",\"contentUrl\":\"https:\\\/\\\/www.redfin.com\\\/news\\\/wp-content\\\/uploads\\\/2020\\\/10\\\/Redfin-News-Logo.png\",\"width\":1100,\"height\":235,\"caption\":\"Redfin\"},\"image\":{\"@id\":\"https:\\\/\\\/www.redfin.com/news\\\/#\\\/schema\\\/logo\\\/image\\\/\"},\"sameAs\":[\"https:\\\/\\\/www.facebook.com\\\/redfin\",\"https:\\\/\\\/x.com\\\/redfin\",\"https:\\\/\\\/www.instagram.com\\\/redfinrealestate\\\/\",\"https:\\\/\\\/www.linkedin.com\\\/company\\\/redfin\",\"https:\\\/\\\/www.pinterest.com\\\/redfin\\\/\",\"https:\\\/\\\/en.wikipedia.org\\\/wiki\\\/Redfin\"]}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Downloads - Redfin","description":"Downloadable datasets covering housing market metrics from across the U.S.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.redfin.com\/news\/data-center\/downloads\/","og_locale":"en_US","og_type":"article","og_title":"Downloads","og_description":"Downloadable datasets covering housing market metrics from across the U.S.","og_url":"https:\/\/www.redfin.com\/news\/data-center\/downloads\/","og_site_name":"Redfin Real Estate News","article_publisher":"https:\/\/www.facebook.com\/redfin","article_modified_time":"2026-05-07T16:56:59+00:00","twitter_card":"summary_large_image","twitter_site":"@redfin","twitter_misc":{"Est. reading time":"1 minute","Written by":"Suzanne Harrison"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/www.redfin.com\/news\/data-center\/downloads\/","url":"https:\/\/www.redfin.com\/news\/data-center\/downloads\/","name":"Downloads - Redfin","isPartOf":{"@id":"https:\/\/www.redfin.com\/news\/#website"},"datePublished":"2026-05-05T23:13:25+00:00","dateModified":"2026-05-07T16:56:59+00:00","description":"Downloadable datasets covering housing market metrics from across the U.S.","breadcrumb":{"@id":"https:\/\/www.redfin.com\/news\/data-center\/downloads\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.redfin.com\/news\/data-center\/downloads\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/www.redfin.com\/news\/data-center\/downloads\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.redfin.com\/news\/"},{"@type":"ListItem","position":2,"name":"Data Center","item":"https:\/\/www.redfin.com\/news\/data-center\/"},{"@type":"ListItem","position":3,"name":"Downloads"}]},{"@type":"WebSite","@id":"https:\/\/www.redfin.com\/news\/#website","url":"https:\/\/www.redfin.com\/news\/","name":"Redfin Real Estate News","description":"The latest real estate news and research from technology-powered residential real estate company, Redfin.","publisher":{"@id":"https:\/\/www.redfin.com\/news\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.redfin.com\/news\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/www.redfin.com\/news\/#organization","name":"Redfin","url":"https:\/\/www.redfin.com\/news\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.redfin.com\/news\/#\/schema\/logo\/image\/","url":"https:\/\/www.redfin.com\/news\/wp-content\/uploads\/2020\/10\/Redfin-News-Logo.png","contentUrl":"https:\/\/www.redfin.com\/news\/wp-content\/uploads\/2020\/10\/Redfin-News-Logo.png","width":1100,"height":235,"caption":"Redfin"},"image":{"@id":"https:\/\/www.redfin.com\/news\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/redfin","https:\/\/x.com\/redfin","https:\/\/www.instagram.com\/redfinrealestate\/","https:\/\/www.linkedin.com\/company\/redfin","https:\/\/www.pinterest.com\/redfin\/","https:\/\/en.wikipedia.org\/wiki\/Redfin"]}]}},"_links":{"self":[{"href":"https:\/\/www.redfin.com\/news\/wp-json\/wp\/v2\/pages\/85151","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.redfin.com\/news\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.redfin.com\/news\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.redfin.com\/news\/wp-json\/wp\/v2\/users\/12371"}],"replies":[{"embeddable":true,"href":"https:\/\/www.redfin.com\/news\/wp-json\/wp\/v2\/comments?post=85151"}],"version-history":[{"count":13,"href":"https:\/\/www.redfin.com\/news\/wp-json\/wp\/v2\/pages\/85151\/revisions"}],"predecessor-version":[{"id":85401,"href":"https:\/\/www.redfin.com\/news\/wp-json\/wp\/v2\/pages\/85151\/revisions\/85401"}],"up":[{"embeddable":true,"href":"https:\/\/www.redfin.com\/news\/wp-json\/wp\/v2\/pages\/24"}],"wp:attachment":[{"href":"https:\/\/www.redfin.com\/news\/wp-json\/wp\/v2\/media?parent=85151"}],"wp:term":[{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.redfin.com\/news\/wp-json\/wp\/v2\/coauthors?post=85151"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}